@outloud/adonis-auth
v1.0.0
Published
Session and access token guards for AdonisJS 7.
Readme
AdonisJS Auth
Session and access token guards for AdonisJS 7, built on top of @adonisjs/auth.
Features
- Database backed sessions with per-platform sliding expiry, sent as a cookie or a bearer token.
- Access tokens with static or per-row prefixes.
- Tokens are checksummed, so tokens with a typo are rejected without a DB lookup.
- Generic
TokenServicefor one-time tokens (password resets, invitations, ...). - Middleware for auth, guest, silent auth and guard restriction.
Getting Started
Install the package and make sure @adonisjs/auth and @adonisjs/lucid are configured.
npm i @outloud/adonis-authTokens
Tokens are stored as a sha256 hash, the plain value is available only right after creation. Token models must have id (uuid), hash, createdAt, updatedAt and optional expiresAt columns.
The token value has the format <prefix>_<id>.<secret><checksum>, the prefix is optional.
import { Secret } from '@adonisjs/core/helpers'
import { TokenService } from '@outloud/adonis-auth'
import PasswordReset from '#models/password_reset'
const tokens = new TokenService(PasswordReset, { prefix: 'pr' })
const { token, row } = await tokens.create({ userId: user.id, expiresAt })
token.value?.release() // plain token, send it to the user
const result = await tokens.verify(new Secret(value)) // { token, row } | null
// verify, run the callback and delete the token, throws errors.E_INVALID_TOKEN when invalid
await tokens.consume(new Secret(value), async (row) => {})Options:
prefix- static prefix, or a function resolving it from the row.secretLength- length of the random secret, defaults to16.query- modify the lookup query, e.g. to preload relations.validate- additional validation of a verified token.
Guards
Session guard
Sessions are stored in a database table. Each session belongs to a platform, which defines its lifetime:
age- idle lifetime, extended on every use.maxAge- absolute lifetime counted from session creation.
The session model must implement SessionContract: platform, data (json), userAgent, ipAddress, lastActivityAt and expiresAt columns on top of the token columns.
// config/auth.ts
import { defineConfig } from '@adonisjs/auth'
import { sessionGuard, sessionUserProvider } from '@outloud/adonis-auth/guards/session'
const authConfig = defineConfig({
default: 'session',
guards: {
session: sessionGuard({
cookie: {
name: 'app.session',
path: '/',
httpOnly: true,
sameSite: 'lax',
},
platforms: {
web: { age: '7d', maxAge: '30d' },
ios: { age: '30d', maxAge: '365d' },
},
provider: sessionUserProvider({
model: () => import('#models/user'),
session: () => import('#models/session'),
foreignKey: 'userId',
prefix: 'ses',
}),
}),
},
})Login creates a session. With cookie transport (default) the session cookie is set, with header transport the token is returned and the client sends it as a bearer token.
const { token } = await auth.use('session').login(user, { platform: 'web', transport: 'header' })The current session is available as auth.session. Changes to auth.session.data are persisted at the end of the request by the silent auth middleware, or manually using auth.use('session').commit().
Expired sessions are deleted in the background, at most once per 10 minutes per process.
Access token guard
// config/auth.ts
import { defineConfig } from '@adonisjs/auth'
import { accessTokenGuard, accessTokenUserProvider } from '@outloud/adonis-auth/guards/access_token'
const authConfig = defineConfig({
default: 'api',
guards: {
api: accessTokenGuard({
provider: accessTokenUserProvider({
model: () => import('#models/user'),
token: () => import('#models/access_token'),
foreignKey: 'userId',
prefix: 'tok',
}),
}),
},
})The current token is available as auth.accessToken. When the token model has a lastActivityAt column, it is updated at most once per minute.
Middleware
Register the middleware in start/kernel.ts.
router.use([
() => import('@adonisjs/auth/initialize_auth_middleware'),
() => import('@outloud/adonis-auth/middleware/silent_auth'),
])
export const middleware = router.named({
guest: () => import('@outloud/adonis-auth/middleware/guest'),
auth: () => import('@outloud/adonis-auth/middleware/auth'),
guard: () => import('@outloud/adonis-auth/middleware/guard'),
})silent_auth- attempts to authenticate the request without failing it, commits guard changes (e.g. session data) once the request is handled.auth- denies the request unless it is authenticated by one of the guards.guest- denies the request when it is authenticated, throwserrors.E_GUEST_ACCESS.guard- denies the request unless it was authenticated by one of the given guards, throwserrors.E_GUARD_REQUIREDwith codeE_<GUARD>_REQUIREDfor a single guard.
auth, guest and silent_auth use all configured guards unless specified.
router.post('/account/delete', handler).use([middleware.auth(), middleware.guard(['session'])])