Many online apps provide a means for users to "login
" and authenticate with the programme. Implementing this functionality in web applications might be a difficult and dangerous task. As a result, OstroJS makes every effort to provide you all the tools you need to create authentication fast, safely, and effortlessly.
The authentication facilities in OstroJS are built up of "guards
" and "providers
" at their core. For each request, guards determine how users are authenticated. OstroJS, for example, has a session
guard that uses session storage and cookies to keep state.
Users are obtained from your persistent storage via providers. Using Eloquent and the database query builder, OstroJS provides functionality for fetching users. You can, however, establish as many extra providers as you require for your application.
The authentication configuration file for your application is config/auth.js. This file includes a number of well-documented options for modifying OstroJS authentication services' behaviour.
In your app/Models
directory, OstroJS provides an app/Models/User
Eloquent model by default. This model is compatible with Eloquent's default authentication driver. You can utilise the database
authentication provider, which uses the OstroJS query builder, if your application does not use Eloquent.
Make that the password column in the database schema for the app/Models/User
model is at least 60 characters long. Of course, the new OstroJS apps' users
table migration already produces a column that is longer than this.
Also, make sure that your users
(or comparable) database has a 100-character nullable string remember_token
field. This column will be used to hold a token for users who choose to "remember me" when logging into your app. This field is already included in the default users
table conversion that comes with new OstroJS apps.
Authentication is covered by numerous packages in OstroJS. Before moving on, let's take a look at OstroJS's overall authentication ecosystem and the functions of each package.
Consider how authentication works first. A user will enter their username and password into a login form while using a web browser. The programme will keep information about the authenticated user in the user's session if these credentials are correct. The session ID is stored in a cookie that is sent to the browser, allowing subsequent requests to the application to associate the user with the right session. The programme will obtain the session data based on the session ID, note that the authentication information has been stored in the session, and consider the user "authenticated" after receiving the session cookie.
Because there is no web browser, cookies are normally not utilised for authentication when a remote service wants to access an API. Rather, the
The Auth
and Session
facades are commonly used to access OstroJS's built-in authentication and session services. For requests initiated from web browsers, these capabilities allow cookie-based authentication. They offer techniques for verifying a user's credentials and authenticating the user. Furthermore, these services will keep the appropriate authentication data in the user's session and issue the user's session cookie automatically. This documentation includes instructions on how to utilise these services.
Passport and Sanctum are two optional packages provided by OstroJS to help you manage API tokens and authenticate requests performed with API tokens. These libraries are not mutually exclusive with OstroJS's built-in cookie-based authentication libraries. The built-in authentication services rely on cookie-based browser authentication, whereas these libraries primarily focus on API token authentication. Many apps will make use of both OstroJS's built-in cookie-based authentication and one of the API authentication packages.
You'll frequently need to communicate with the presently authenticated user after installing an authentication starter kit and allowing people to register and authenticate with your application. The authorised user may be accessed using the Auth
facade's user
function when processing an incoming request:
const Controller = require('~/app/http/controllers/controller')
class UserController extends Controller{
async details({auth}){
let user = await auth.user();
// Retrieve the currently authenticated user's ID...
let id = await auth.id();
}
}
module.exports = UserController
Alternatively, you may use an @ostro/http/request
instance to reach a user who has been authorised. Remember that type-hinted classes will be inserted into your controller methods automatically. You may obtain quick access to the authorised user from any controller function in your application by type-hinting the @ostro/http/request
object's user
method:
const Controller = require('~/app/http/controllers/controller')
class UserController extends Controller{
async details({request}){
let user = await request.user();
let id = await auth.id();
}
}
module.exports = UserController
You may use the check
method on the Auth
façade to see if the user making the incoming HTTP request is authenticated. If the user is authorised, this method will return true
:
if (await auth.check()) {
// The user is logged in...
}
Route middleware may be used to restrict access to a route to only authenticated users. OstroJS includes an auth
middleware that uses the @ostro/auth/middleware/authenticate
class. Because this middleware is already registered in the HTTP kernel of your application, all you have to do now is connect it to a route definition:
Route.get('/flights', function () {
// Only authenticated users may access this route...
}).middleware('auth');
The auth
middleware will send an unauthenticated user to the login
specified route if it finds it. You may change this behaviour by modifying the redirectTo
function in the app/http/middleware/authenticate
file of your application:
/**
* Get the path the user should be redirected to.
*
*/
redirectTo({request}){
return route('login');
}
You may additionally specify which "guard" should be used to authenticate the user when connecting the auth
middleware to a route. The guard
given should match one of the keys in your auth.js
configuration file's guards array:
Route.get('/flights', function () {
// Only authenticated users may access this route...
}).middleware('auth:admin');
The authentication scaffolding offered with OstroJS's application starter kits is optional. If you don't utilise this scaffolding, you'll have to use the OstroJS authentication classes to manage user authentication. Don't worry, it'll be easy!
Because we'll be using the Auth
facade to access OstroJS's authentication services, we'll need to import it at the start of the class. Let's have a look at the attempt
approach next. Authentication attempts from your application's "login" form are typically handled by the attempt
function. To avoid session fixation, you should regenerate the user's session if authentication is successful:
const Controller = require('~/app/http/controllers/controller');
class LoginController extends Controller {
async login({ request, validate, auth, redirect }) {
await this.validate(request, {
'email': 'required|email',
'password': 'required'
})
if(await auth.attempt(credentials)){
return redirect.route('admin.dashboard.index');
}
return redirect.withInput().withErrors({username:['Invalid username password']}).back();
}
}
module.exports = LoginController
The first input to the attempt
function is an array of key/value pairs. The array's values will be utilised to locate the user in the database table. As a result, the value of the email
column will be used to obtain the user in the example above. If the user is discovered, the database's hashed password
is compared to the password
value given to the procedure through the array. Because the framework will automatically hash the value before comparing it to the hashed password in the database, you should not hash the incoming request's password value. If the two hashed passwords match, an authenticated session will be initiated for the user.
Remember that OstroJS's authentication services will extract users from your database based on the "provider" option of your authentication guard. The Eloquent user provider is given in the default config/auth.js
configuration file, and it is directed to retrieve users using the app/Models/User
model. Depending on your application's demands, you can adjust these settings in your configuration file.
If authentication was successful, the attempt
method will return true
. false
will be returned if this is not the case.
Before being intercepted by the authentication middleware, the intended
method offered by OstroJS's redirector would redirect the user to the URL they were intending to visit. If the desired destination is not accessible, this function might be supplied a fallback URI.
In addition to the user's email and password, you may add more query criteria to the authentication query if you like. Simply add the query criteria to the array supplied to the try function to do this. We may, for example, check that the user is listed as "active":
if (await auth.attempt({'email' : $email, 'password' : $password, 'active' : 1})) {
// Authentication was successful...
}
You may specify the guard instance to use while authenticating the user using the Auth
facade's guard
method. This lets you to manage authentication for different portions of your programme using completely different authenticatable models or user tables.
One of the guards specified in your auth.js
configuration file should match the guard name supplied to the guard
method:
if (await auth.guard('admin').attempt($credentials)) {
// ...
}
On the login form of many web programmes, there is a "remember me" checkbox. You may send a boolean value as the second argument to the attempt
method if you want to include "remember me" functionality in your app.
OstroJS will keep the user authorised indefinitely or until they manually logout if this value is true
. The string remember_token
column in your users
table must be present, since it will be used to hold the "remember me" token. This column is already included in the users
table migration bundled with new OstroJS applications:
if (await auth.attempt({'email' : $email, 'password' : $password, 'active' : 1}, $remember)) {
// Authentication was successful...
}
You can give an existing user instance to the Auth
facade's login
function if you need to set it as the currently authenticated user. The provided user instance must be an @ostro/contracts/auth/authenticatable
contract implementation. This interface is already implemented in OstroJS's app/models/user
model. When you already have a valid user instance, such as when a user registers with your application, this way of authentication is useful:
Auth.login($user);
The second argument to the login
function can be a boolean value. This value determines whether the authorised session should include "remember me" capabilities. Remember that the session will be authenticated continuously or until the user logs out of the application manually:
// remember should be boolean true/false
await auth.login($user, $remember);
Before invoking the login
function, you can specify an authentication guard if necessary:
await auth.guard('admin').login($user);
The loginUsingId
function may be used to authenticate a user using their database record's primary key. The main key of the user you want to authenticate is accepted by this method:
await auth.loginUsingId(1);
The loginUsingId
function accepts a boolean value as the second input. This value determines whether the authorised session should include "remember me" capabilities. Remember that the session will be authenticated continuously or until the user logs out of the application manually:
await auth.loginUsingId(1, $remember);
For a single request, you may use the once
method to authenticate a user with the application. When calling this method, no sessions or cookies will be used:
if (await auth.once($credentials)) {
//
}
Without establishing a user identifying cookie in the session, you may utilise HTTP Basic Authentication. If you want to utilise HTTP Authentication to authenticate calls to your application's API, this is very useful. Define a middleware that calls the onceBasic
function to do this. If the onceBasic
method does not produce a response, the request can be sent further into the application:
class AuthenticateOnceWithBasicAuth {
/**
* Handle an incoming request.
*
*/
async handle({auth, next}) {
return await auth.onceBasic() || next();
}
}
module.exports = AuthenticateOnceWithBasicAuth
You may use the Auth
facade's logout
function to manually log users out of your application. The authentication information will be removed from the user's session, and subsequent requests will not be authenticated.
It is suggested that you invalidate the user's session and renew their CSRF token in addition to invoking the logout procedure. You would generally send the user to the root of your programme after logging them out:
class UserController{
/**
* Log the user out of the application.
*
*/
async logout({session,redirect}) {
await auth.logout();
session.invalidate();
session.regenerateToken();
redirect('/');
}
}
module.exports = UserController
You may encounter actions in your programme that need the user to confirm their password before the action is completed or before the user is routed to a sensitive region of the application while developing it. The built-in middleware in OstroJS makes this procedure a breeze. You'll need to design two routes to implement this feature: one to display a view requesting the user to confirm their password, and another to ensure the password is genuine and redirect the user to their intended destination.
Then, from the "confirm password" view, we'll construct a route to handle the form request. This route will be in charge of confirming the user's password and forwarding them to their desired location:
const Route = require('@ostro/support/facades/route')
const Hash = require('@ostro/support/facades/hash')
Route.post('/confirm-password', function ({request,redirect}) {
if (! Hash.check(request.input('password'), (await request.user()).password)) {
return redirect.withErrors({
'password' : ['The provided password does not match our records.']
}).back();
}
return redirect.to('/');
}).middleware(['auth']).name('password.confirm');
Using the extend
function on the Auth
facade, you may construct your own authentication guards. Within a service provider, you should invoke the extend
method. We can use the AuthServiceProvider
provided by OstroJS to put the code:
const ServiceProvider = require('@ostro/support/serviceProvider')
class AuthServiceProvider extends ServiceProvider {
/**
* Register any application authentication / authorization services.
*
* @return void
*/
boot() {
this.$app.auth.extend('jwt', function ($app, $name, $config) {
// Return an instance of @ostro/contracts/auth/guard...
return new JwtGuard(this.createUserProvider($config['provider']));
});
}
}
module.exports = AuthServiceProvider
As seen in the example above, the callback supplied to the extend
function should return an @ostro/contracts/auth/guard
implementation. To define a custom guard, you'll need to implement a couple methods from this interface. After you've defined your own guard, you can reference it in the guards
configuration of your auth.js
configuration file:
'guards' : {
'api' : {
'driver' : 'jwt',
'provider' : 'users',
},
},
You'll need to augment OstroJS with your own authentication user provider if you're not utilising a typical relational database to store your users. To define a custom user provider, we'll utilise the provider
method on the Auth
façade. The resolver for the user provider should return an implementation of
@ostro/contracts/auth/userProvider:
const ServiceProvider = require('@ostro/auth/authServiceProvider')
class AuthServiceProvider extends ServiceProvider {
/**
* Register any application authentication / authorization services.
*
* @return void
*/
boot() {
this.$app.auth.provider('mongo', function ($app, array $config) {
// Return an instance of @ostro/contracts/auth/userProvider...
return new MongoUserProvider($app.make('mongo.connection'));
});
}
}
module.exports = AuthServiceProvider
You may switch to the new user provider
in your auth.js
configuration file once you've registered the provider using the provider
method. To begin, create a provider that will use your new driver:
'providers' : {
'users' : {
'driver' : 'mongo',
},
},
Finally, you can use the following code in your guards
configuration to refer to this provider:
'guards' : {
'web' : {
'driver' : 'session',
'provider' : 'users',
},
},
Implementations of @ostro/contracts/auth/UserProvider are responsible for retrieving an @ostro/contracts/auth/authenticatable implementation from a persistent storage system like MySQL or MongoDB. These two interfaces ensure that the OstroJS authentication techniques work independent of how user data is stored or what sort of class is used to represent the authorised user:
Take a look at the contract @ostro/contracts/auth/userProvider:
class UserProvider {
retrieveById($identifier);
retrieveByToken($identifier, $token);
updateRememberToken($user, $token);
retrieveByCredentials($credentials = {});
validateCredentials($user, $credentials = {});
}
A key describing the user, such as an auto-incrementing ID from a MySQL database, is commonly sent to the retrieveById method. The method should retrieve and return the Authenticatable implementation that matches the ID.
The retrieveByToken method gets a user's unique $identifier and "remember me" $token, which are usually kept in a database column called remember token. This method, like the last one, should return an Authenticatable implementation with a matching token value.
The updateRememberToken function replaces the remember token of the $user object with the updated $token. After a successful "remember me" authentication attempt or when the user logs out, a new token is issued to them.
The array of credentials supplied to the Auth::attempt function when attempting to authenticate with an application is passed to the retrieveByCredentials method. After that, the procedure should "query" the underlying persistent storage for a user with those credentials. This function will often execute a query with a "where" condition that looks for a user record with a "username" that matches the value of $credentials['username']. The method should return an Authenticatable implementation. This method should not attempt to validate or authenticate any passwords.
To authenticate the user, the validateCredentials function should check the provided $user with the $credentials. To compare the value of $user->getAuthPassword() to the value of $credentials['password'], this function will often utilise the Hash::check method. If the password is valid, this function should return true or false.
Let's look at the Authenticatable contract now that we've looked at each of the methods on the UserProvider. Remember that the retrieveById, retrieveByToken, and retrieveByCredentials functions should return implementations of this interface:
class Authenticatable {
getAuthIdentifierName();
getAuthIdentifier();
getAuthPassword();
getRememberToken();
setRememberToken($value);
getRememberTokenName();
}
This user interface is straightforward. The name of the user's "primary key" field should be returned by the getAuthIdentifierName method, and the user's "primary key" should be returned by the getAuthIdentifier method. This is most likely the auto-incrementing primary key supplied to the user record when utilising a MySQL backend. The user's hashed password should be returned by the getAuthPassword function.
This interface enables the authentication system to operate with any "user" class, independent of the ORM or storage abstraction layer that is being used. OstroJS comes with an AppModelsUser class in the app/Models directory that implements this interface by default.