Introduction

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.

Database Considerations

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.

Ecosystem Overview

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

OstroJS's Built-in Browser Authentication Services

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.

OstroJS's API Authentication 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.

Authentication Quickstart

Retrieving The Authenticated User

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

Determining If The Current User Is Authenticated

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...
}

Protecting Routes

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');

Redirecting Unauthenticated Users

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');
}

Specifying A Guard

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');

Manually Authenticating Users

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.

Specifying Additional Conditions

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...
}

Accessing Specific Guard Instances

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)) {
    // ...
}

Remembering Users

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...
}

Other Authentication Methods

Authenticate A User Instance

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);

Authenticate A User By ID

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);

Authenticate A User Once

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)) {
    //
}

Stateless HTTP Basic Authentication

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

Logging Out

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

Password Confirmation

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.

Confirming The Password

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');

Adding Custom Guards

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',
    },
},

Adding Custom User Providers

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',
    },
},

The User Provider Contract

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.

The Authenticatable Contract

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.