Support AdonisJs development by becoming a patron
Support AdonisJs

Authentication

Every Web application deals with User Management and Authentication at some stage. AdonisJs Authentication provider is a fully featured system to authenticate HTTP requests using multiple authenticators. Using authenticators, you can build traditional session based login systems to secure REST API’s.

Authenticators

Each authenticator is a combination of serializers and an authentication scheme.

Schemes

  • Sessions (session)

  • Basic Auth (basic)

  • JWT (jwt)

  • Personal API Tokens (api)

Serializers

  • Lucid

  • Database

About Authentication

  1. Authentication is divided into two broad categories of Stateful authentication and Stateless authentication.

  2. Session based authentication is considered Stateful Authentication, since once logged in the user can navigate to different areas of the application without resending the credentials.

  3. Stateless Authentication requires the end-user to send the credentials on each HTTP request which is very common with REST API’s.

  4. AdonisJs provides required tooling & helpers to manage both types of authentication with ease.

  5. Authentication provider will make use of the Hash module to verify passwords. Make sure to hash your passwords before saying them to the database.

Config

Configuration for authentication is saved inside config/auth.js file. By default, it makes use of the session authenticator to authenticate requests.

module.exports = {
  authenticator: 'session',
  session: {
    serializer: 'Lucid',
    scheme: 'session',
    model: 'App/Model/User',
    uid: 'email',
    password: 'password'
  }
}
Key Values Description

serializer

Lucid, Database

Serializer to be used for fetching the user from the database.

scheme

session, basic, jwt, api

Scheme to be used for fetching and authenticating user credentials.

uid

Database field name

Database field to be used as unique identifier for a given user.

password

Database field name

Field to be used for verifying user password

model

Model Namespace (Lucid Only)

Applicable only when using Lucid serializer. Given model will be used for querying the database.

table

Database table name (Database Only)

Applicable only when using Database serializer.

Migrations & Models

Authentication provider can generate required migrations for you using an ace command. Just make sure following command is added to the list of commands.

bootstrap/app.js
const commands = [
  ...,
  'Adonis/Commands/Auth:Setup'
]
Running Setup Command
./ace auth:setup
Output
create: app/Model/User.js
create: database/migrations/1463212428511_User.js
create: app/Model/Token.js
create: database/migrations/1463212428512_Token.js

Basic Example

Let’s start with a basic example of creating a simple app to login and displaying the profile of a logged in user.

app/Http/routes.js
'use strict'

const Route = use('Route')

Route.get('/profile', 'UserController.profile')
Route.post('/login', 'UserController.login')
app/Http/Controllers/UserController.js
'use strict'

class UserController {

  * login (request, response) {
    const email = request.input('email')
    const password = request.input('password')
    const login = yield request.auth.attempt(email, password) (1)

    if (login) {
      response.send('Logged In Successfully')
      return
    }

    response.unauthorized('Invalid credentails')
  }

}
1 Once authentication provider is configured, you can make use of the auth property on the request instance to authenticate a user or check the authentication status.

Let’s write another method to show the profile of a user only if they are logged in.

Showing User Profile
* profile (request, response) {
  const user = yield request.auth.getUser()
  if (user) {
    response.ok(user)
    return
  }
  response.unauthorized('You must login to view your profile')
}

Session Based Authentication

Below is the list of methods you can use on session authenticator.

attempt(uid, password)

Attempt to log in a user using the uid and password. It will throw an error if unable to find the user or if password mismatch.

try {
  yield request.auth.attempt(uid, password)
} catch (e) {
  yield request.with({error: e.message}).flash()
  response.redirect('back')
}

login(user)

Login a user using the user model instance.

const user = yield User.find(1)
try {
  yield request.auth.login(user)
  response.redirect('/dashboard')
} catch (e) {
  yield request.with({error: e.message}).flash()
  response.redirect('back')
}

loginViaId(id)

Login user using the id. A database lookup will be performed to make sure that the user does exist otherwise an exception will be raised.

try {
  yield request.auth.loginViaId(1)
  response.redirect('/dashboard')
} catch (e) {
  yield request.with({error: e.message}).flash()
  response.redirect('back')
}

logout

Logout an existing logged in user.

yield request.auth.logout()

check

Check to see if a user is logged in or not.

const isLoggedIn = yield request.auth.check()
if (!isLoggedIn) {
  response.redirect('/login')
}

validate(uid, password)

Validate user credentials to see if they are valid. This method will never set any session/cookie.

try {
  yield request.auth.validate(uid, password)
} catch (e) {
  yield request.with({error: e.message}).flash()
  response.redirect('back')
}

Basic Auth

Below is the list of methods available for basic auth authenticator.

Basic auth credentials are base64 encoded and sent as the Authorization header. Example: Authorization=username:password

check

Check to see if basic auth credentials are present in the Authorization header.

const isLoggedIn = yield request.auth.check()
if (!isLoggedIn) {
  response.redirect('/login')
}

validate(uid, password)

Validate user credentials to see if they are valid or not.

try {
  yield request.auth.validate(uid, password)
} catch (e) {
  response.unauthorized({error: e.message})
}

JWT

JWT authenticators require a couple of extra attributes to the configuration block.

JWT token is sent as the Authorization header. Example: Authorization=Bearer JWT_TOKEN
config/auth.js
{
  authenticator: 'jwt',
  jwt: {
    serializer: 'Lucid',
    scheme: 'jwt',
    model: 'App/Model/User',
    secret: Config.get('app.appKey'),
    options: {
      // Options to be used while generating token
    }
  }
}
Table 1. Additional Options
Key Available Values Default Value Description

algorithm

HS256, HS384

HS256

Algorithm to be used for generating token

expiresIn

valid time in seconds or ms string

null

When to expire the token

notBefore

valid time in seconds or ms string

null

Till when at least to keep the token valid

audience

String

null

A value to be checked against the aud

issuer

Array or String

null

Value to be used for iss.

subject

String

null

A value to be checked against the sub.

check

Works same as others

const isLoggedIn = yield request.auth.check()
if (!isLoggedIn) {
  response.unauthorized({error: 'Your must be loggedin to access this resource.'})
}

generate(user)

Generates a JWT token for a given user.

const user = yield User.find(1)
const token = yield request.auth.generate(user)

validate(uid, password)

Validate user credentials to see if they are valid or not.

try {
  yield request.auth.validate(uid, password)
} catch (e) {
  response.unauthorized({error: e.message})
}

attempt(uid, password)

Validates the user credentials and generates a JWT token if they valid.

try {
  const token = yield request.auth.attempt(uid, password)
} catch (e) {
  response.unauthorized({error: e.message})
}

API Tokens

Personal API tokens are like the password for a given account. The majority of Web applications offers API-based authentication so that their customers can generate these tokens for developers without sharing their actual Login details.

API token is sent as the Authorization header. Example: Authorization=Bearer API_TOKEN
  1. API tokens are saved to the database corresponding to a given user.

  2. You can set expiry for a token or set it null for never expiring tokens.

  3. You can revoke a single/all tokens for a given user.

config/auth.js
authenticator: 'api',
api: {
  serializer: 'Lucid',
  scheme: 'api',
  model: 'App/Model/Token',
  expiry: '30d'
}

Setting Up Token/User Relationship

The Token Model needs to have a relationship with the User model to save tokens with ease. Make sure to define the relationship between both the models as defined below.

Make use of auth:setup command to generate the models/migrations and set relationships for you.
app/Model/Token.js
'use strict'

const Lucid = use('Lucid')

class Token extends Lucid {
  user () {
    return this.belongsTo('App/Model/User')
  }
}
app/Model/User.js
'use strict'

const Lucid = use('Lucid')

class User extends Lucid {
  apiTokens () {
    return this.hasMany('App/Model/Token')
  }
}

Methods

Below is the list of available methods to be used with the api authenticator.

check

Works same as others

const isLoggedIn = yield request.auth.check()

generate(user, [expiry])

Generate an API token for a given user and save it to the database.

const user = yield User.find(1)
const token = yield request.auth.generate(user)

revoke(user, tokens=Array)

Revoke/Delete given tokens for a given user.

const user = yield User.find(1)
yield request.auth.revoke(user, [token])

revokeAll(user)

Revoke/Delete all tokens for a given user.

const user = yield User.find(1)
yield request.auth.revokeAll(user)

revokeExcept(user, tokens=Array)

Revoke all tokens except the given ones.

const user = yield User.find(1)
yield request.auth.revokeExcept(user, [token])

Securing Routes

So far you have been authenticating users manually, which can lead to duplicate code in multiple controllers. AdonisJs Auth Middleware can automatically authenticate the routes and makes sure to deny the requests when the end-user is not logged in.

Make sure the Auth Middleware is registered as a named middleware inside app/Http/kernel.js file.

app/Http/kernel.js
const namedMiddleware = {
  auth: 'Adonis/Middleware/Auth'
}

Now you are all set to take advantage of the auth middleware on your routes.

app/Http/routes.js
Route
  .get('/user/profile', 'UserController.profile')
  .middleware('auth')

Also, you can define a different authenticator by passing arguments to the auth middleware at runtime.

Route
  .get('/user/profile', 'UserController.profile')
  .middleware('auth:basic')

Switching Between Authenticators

You can also switch between different authenticators using the authenticator method.

const jwt = request.auth.authenticator('jwt')
const basicAuth = request.auth.authenticator('basic')
const api = request.auth.authenticator('api')

yield jwt.check()
yield basicAuth.check()
yield api.check()

Helpers

Helpers makes it easier to retrieve the currently logged in user during an HTTP request.

Session Based

You can access the currentUser property on the request object and as a global inside your views when the end-user is logged in via session authenticator.

request.currentUser // logged in user
{{ currentUser }}

All Other Authenticators

All other authenticators like JWT, Basic Auth and API Token will have access to the currently logged in user as authUser property on the request object.

request.authUser // authenticated user instance

Extending Auth Provider

It is fairly simple to extend the provider by adding new serializers or schemes. The important step is to understand the need of both.

bootstrap/extend.js
Ioc.extend('Adonis/Src/AuthManager', 'mongo', function (app) {
  return new MongoSerializer()
}, 'serializer')

// Or

Ioc.extend('Adonis/Src/AuthManager', 'fingerprint', function (app) {
  // adonis will initiate the scheme itself for each request.
  return FingerPrint
}, 'scheme')

Serializer

Serializer is used to serialize/fetch the user from the data store using their unique identifier. Also serializer is suppose to verify the user password.

class MongoSerializer {

  * findById (id, options) {
  }

  * findByCredentials (email, options) {
  }

  * findByToken (token, options) {
  }

  * getUserForToken (tokenPayload, options) {
  }

  * saveToken (userPayload, token, options, expiry) {
  }

  * revokeTokens (userPayload, tokens, reverse) {
  }

  * validateToken (tokenPayload, options) {
  }

  * validateCredentials (userPayload, password, options) {
  }

  primaryKey(authenticatorOptions) {
  }
}
  1. findById - This method should find a user using the unique identifier and return the user object. For example: Lucid serializer will return the User model instance.

  2. findByCredentials - The method will find a user using the field name (uid) defined inside the config/auth.js file and must return the user object.

  3. findByToken - This method should return the token object using a unique token.

  4. getUserForToken - Here we return the user object using the token object returned by findByToken method.

  5. saveToken - Save token for a given user. The token is generated by auth provider itself and you must save it for later use.

  6. revokeTokens - Revoke a single/multiple tokens. If reverse=true you must revoke all the tokens except the one passed as the 2nd parameter.

  7. validateToken - Here you must validate the token payload returned by findByToken method. The most common check is to verify the expiry.

  8. validateCredentials - This method is used to verify the user password against the plain password.

  9. primaryKey — This method is used to get primary key definition to make sure that the primary key is not null for the user.

Schemes

Schemes defines the way of authenticating users. For example: Session, JWT, Basic Auth etc. You can add your own schemes if required. Below is the list of methods your scheme should implement.

All the methods of your schemes are exposed to the end-user. Which means they can call these methods directly using the auth property on the request object.
class FingerPrint {

  constructor (request, serializer, options) {
    this.request = request
    this.serializer = serializer
    this.options = options // config options
  }

  * check () {
  }

  * getUser () {
  }

}
  1. check - Check method should return a boolean indicating whether a user is logged in or not. You can access the values of the current request using the request parameter passed to the constructor.

  2. getUser - Should return the user payload only if user is logged in. Otherwise it should return null