Getting Started

How to setup authentication in Tensei applications

Tensei ships with a fully featured and cutomisable authentication system via the @tensei/auth plugin package. Out of the box you get:

  • JWT authentication
  • Refresh tokens and refresh token rotation
  • Social authentication
  • Role based access control
  • Email verification & Confirmation
  • Password resets
  • Two-factor authentication
  • User blocking / Unblocking

Installation

You may install the auth plugin with the following command:

yarn add @tensei/auth

# Or with nom 
npm install --save @tensei/auth

Register the plugin

You may register the auth plugin by adding it to the plugins() method of your tensei instance:

import { auth } from '@tensei/auth'
import { tensei } from '@tensei/core'

tensei()
    .plugins([
        auth().plugin()
    ])

Make sure this plugin is registered before the graphql() or rest() plugins, that way, it can register new routes, queries and types before the API is booted.

JWT authentication

Tensei implements the best practices for JWT and refresh tokens authentication as recommended by the Internet Engineering Task Force.

By default, the auth plugin adds the following types and queries to the GraphQL API:

input insert_user_input {
  email: String!
  password: String!
}

type user {
    id: ID!
    email: String
    created_at: String
    updated_at: String
}

type login_user_response {
  access_token: String!
  expires_in: Int!
  user: user!
}

type register_user_response {
  access_token: String!
  expires_in: Int!
  user: user!
}

input reset_user_password_input {
  email: String!
  token: String!
  password: String!
}

input request_user_password_reset_input {
    email: String!
}

extend type Mutation {
    logout_user: Boolean!
    login_user(object: login_user_input!): login_user_response!
    register_user(object: insert_user_input!): register_user_response!

    reset_user_password(object: reset_user_password_input!): Boolean!
    request_user_password_reset(object: request_user_password_reset_input!): Boolean!
}

extend type Query {
    authenticated_user: user!
}

If you're using the REST plugin, the following endpoints are added:

  • POST auth/register for registering new users
  • POST auth/login for creating new user sessions
  • POST auth/logout for logging out an authenticated user
  • GET auth/me for getting the currently authenticated user
  • POST auth/passwords/email for requesting a password reset
  • POST auth/passwords/reset for reset a user's password

User registration

This provides a way for adding new users to your application. By default, only two fields are accepted: email and password. The password is automatically hashed before storing in the database.

It responds with an access token and the newly created customer:

import axios from 'axios'

async function register() {
  import { data: { user, access_token } } = await axios.post('http://localhost:8810/auth/register', {
      email: 'hey@tenseijs.com',
      password: 'password'
  })
}

If you're using GraphQL, here's how you'll call the register_user mutation:

import { GraphQLClient } from "graphql-request";

const client = new GraphQLClient('http://localhost:8810/graphql')

const REGISTER_USER = gql`
    mutation register_user($email: String!, $password: String!) {
        register_user(object: {
            email: $email,
            password: $password
        }) {
            user {
                id
                created_at
            }
        }
    }
`

// Pass in the query and the variables
client.request(REGISTER_USER, {
    email: 'hey@tenseijs.com',
    password: 'password'
}).then(console.log)

User login

This provides a way to get an access token for a user. Only two fields are accepted: email and password. This endpoint compares the password to make sure it matches, and generates a JWT for the user.

  • The access_token is a valid JWT, and by default, this token expires in 20 minutes.
  • The expires_in is the amount of time left until the access token expires.

Password resets

When a user forget's their password, they can go through the password reset flow to identify and reset to a new password. Here's how this flow should go:

  • First, the user provides their email on your client-side, and you call the POST auth/passwords/email endpoint or request_user_password_reset mutation with this email. The auth plugin will find this user, and if the user exists, will send an email to the user with a unique password resets token embedded in a confirmation link.
const FORGOT_PASSWORD = gql`
    mutation request_user_password_reset($email: String!) {
        request_user_password_reset(object: {
            email: $email
        })
    }
`

client.query({
    query: FORGOT_PASSWORD,
    variables: {
        email: 'hey@tenseijs.com'
    }
}).then(console.log)
  • Secondly, the user visits their email and clicks on the confirmation link. This link sends them back to your client-side application. This action should take them to a form requesting their new password.

  • Next, you need to extract the password reset token from the query string, and make a call to the POST auth/passwords/reset endpoint or reset_user_password mutation with the reset token and new password to reset the password.

const RESET_PASSWORD = gql`
    mutation reset_user_password($password: String!, $token: String!) {
        reset_user_password(object: {
            password: $password,
            token: $token
        })
    }
`

// Pass in query and variables
client.request(RESET_PASSWORD, {
    token: 'gj6uyxxfg359kndc9dvwg286pk5whcnzm8h34xdq828cargxn94',
    password: 'new-password'
}).then(console.log)

Get authenticated user

After login, to get the currently authenticated user, you may call the GET auth/me endpoint or the authenticated_user query. Make sure to set the access token as the authorization header.

The backend server validates this access token, identifies the user, and responds with the user's details.

Here's an example on how to get the authenticated user with axios:

import Axios from 'axios'

const instance = Axios.create({
    baseURL: 'http://localhost:8810/api',
    headers: {
      Authorization: 'Bearer <access_token>'
    }
})

instance.get('auth/me').then(console.log)

Refresh tokens

Refresh tokens provide a way to get new access tokens without the user having to login again. This is great for mobile applications, and in web applications where we want to keep the user logged in for long periods of time.

  • The access_token is a valid JWT, and by default, this token expires in 20 minutes.
  • The refresh_token is a unique random token, which by default expires in 6 months.
  • The expires_in is the amount of time left until the access token expires.

Refresh tokens are disabled by default. To enable refresh tokens, call the .refreshTokens() method.

import { auth } from '@tensei/auth'
import { tensei } from '@tensei/core'

tensei()
    .plugins([
        auth()
          .refreshTokens()
          .plugin()
    ])

Now, when you register or login a user, the refresh token would be added to the response.

New queries are automatically generated to handle refreshing tokens and revoking tokens:

input refresh_user_token_input {
  refresh_token: String
}

export type Mutation {
  refresh_user_access_token(object: refresh_user_token_input): login_user_response!
  revoke_user_refresh_token(object: refresh_user_token_input): login_user_response!
}