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:
You may install the auth plugin with the following command:
yarn add @tensei/auth
# Or with nom
npm install --save @tensei/auth
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.
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 usersPOST auth/login
for creating new user sessionsPOST auth/logout
for logging out an authenticated userGET auth/me
for getting the currently authenticated userPOST auth/passwords/email
for requesting a password resetPOST auth/passwords/reset
for reset a user's passwordThis 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)
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.
access_token
is a valid JWT, and by default, this token expires in 20 minutes.expires_in
is the amount of time left until the access token expires.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:
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)
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 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.
access_token
is a valid JWT, and by default, this token expires in 20 minutes.refresh_token
is a unique random token, which by default expires in 6 months.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!
}