Overview
The AdonisJS Starter Kit includes a complete API token system for authenticating API requests. Administrators can generate personal access tokens to authenticate programmatic access to your application’s API.
Token Management
Viewing Tokens
Users can view all their active API tokens:
app/users/controllers/tokens_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#users/models/user'
import TokenDto from '#users/dtos/token'
import TokenPolicy from '#users/policies/token_policy'
export default class TokensController {
async index ({ auth , bouncer , inertia } : HttpContext ) {
const user = await User . findOrFail ( auth . user ! . id )
await bouncer . with ( TokenPolicy ). authorize ( 'viewList' )
const tokens = await User . accessTokens . all ( user )
return inertia . render ( 'users/tokens' , {
tokens: TokenDto . fromArray ( tokens ),
})
}
}
Creating Tokens
Generate new API tokens with custom names:
app/users/controllers/tokens_controller.ts
async store ({ auth , bouncer , request }: HttpContext ) {
const user = await User . findOrFail ( auth . user ! . id )
await bouncer . with ( TokenPolicy ). authorize ( 'create' )
const payload = await request . validateUsing ( createTokenValidator )
const token = await User . accessTokens . create ( user , undefined , {
name: payload . name ? payload . name : 'Secret Token' ,
})
return {
type: token . type ,
token: token . value ! . release (),
}
}
Token Security : The token value is only shown once after creation. Make sure to copy and store it securely, as it cannot be retrieved later.
Deleting Tokens
Revoke API tokens when they’re no longer needed:
app/users/controllers/tokens_controller.ts
async destroy ({ auth , params , response }: HttpContext ) {
const user = await User . findOrFail ( auth . user ! . id )
await User . accessTokens . delete ( user , params . id )
return response . redirect (). toRoute ( 'tokens.index' )
}
Token Authorization
Only administrators can create and manage API tokens:
app/users/policies/token_policy.ts
import { BasePolicy } from '@adonisjs/bouncer'
import { AuthorizerResponse } from '@adonisjs/bouncer/types'
import User from '#users/models/user'
export default class TokenPolicy extends BasePolicy {
create ( user : User ) : AuthorizerResponse {
return user . isAdmin
}
viewList ( user : User ) : AuthorizerResponse {
return user . isAdmin
}
}
User Model Integration
The User model includes the DbAccessTokensProvider for token management:
import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
import BaseModel from '#common/models/base_model'
export default class User extends compose ( BaseModel , AuthFinder ) {
@ column ({ isPrimary: true })
declare id : number
@ column ()
declare email : string
// ... other fields
static accessTokens = DbAccessTokensProvider . forModel ( User )
}
Authentication Configuration
The api guard is configured for token-based authentication:
import { defineConfig } from '@adonisjs/auth'
import { tokensGuard , tokensUserProvider } from '@adonisjs/auth/access_tokens'
const authConfig = defineConfig ({
default: 'web' ,
guards: {
web: sessionGuard ({
// ... web guard config
}),
api: tokensGuard ({
provider: tokensUserProvider ({
tokens: 'accessTokens' ,
model : () => import ( '#users/models/user' ),
}),
}),
},
})
export default authConfig
API Routes
Token management routes:
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'
const TokensController = () => import ( '#users/controllers/tokens_controller' )
// View tokens
router
. resource ( '/settings/tokens' , TokensController )
. only ([ 'index' , 'destroy' ])
. middleware ( '*' , middleware . auth ())
. as ( 'tokens' )
// Create token (API endpoint)
router . post ( '/api/tokens' , [ TokensController , 'store' ])
. middleware ( middleware . auth ())
Using API Tokens
Making Authenticated API Requests
Include the token in the Authorization header:
curl -X GET https://your-app.com/api/endpoint \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json"
// JavaScript/Node.js
fetch ( 'https://your-app.com/api/endpoint' , {
headers: {
'Authorization' : 'Bearer YOUR_TOKEN_HERE' ,
'Content-Type' : 'application/json'
}
})
# Python
import requests
headers = {
'Authorization' : 'Bearer YOUR_TOKEN_HERE' ,
'Content-Type' : 'application/json'
}
response = requests.get( 'https://your-app.com/api/endpoint' , headers = headers)
Protecting API Routes
Use the api guard to protect API endpoints:
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
router . group (() => {
router . get ( '/users' , [ ApiUsersController , 'index' ])
router . post ( '/posts' , [ ApiPostsController , 'store' ])
router . put ( '/posts/:id' , [ ApiPostsController , 'update' ])
router . delete ( '/posts/:id' , [ ApiPostsController , 'destroy' ])
}). prefix ( '/api' ). middleware (
middleware . auth ({ guards: [ 'api' ] })
)
Accessing Authenticated User
In API controllers, access the authenticated user:
import type { HttpContext } from '@adonisjs/core/http'
export default class ApiUsersController {
async index ({ auth , response } : HttpContext ) {
const user = auth . user ! // User authenticated via token
return response . json ({
user: {
id: user . id ,
email: user . email ,
fullName: user . fullName ,
}
})
}
}
Token Workflow
Generate Token
Administrator creates a new token from the settings page, giving it a descriptive name.
Copy Token
The token value is displayed once. Copy and store it securely (e.g., in environment variables).
Use Token
Include the token in the Authorization header of API requests: Bearer YOUR_TOKEN.
Revoke Token
When the token is no longer needed, delete it from the tokens page to revoke access.
Token Storage
Tokens are stored in the database with the following structure:
Hash : The hashed token value (for security)
Name : User-provided description
Expiry : Optional expiration date
Created At : Token creation timestamp
Last Used At : When the token was last used
Security : Only the hashed version of tokens is stored in the database. The plain text token is only available at creation time.
Token Best Practices
Descriptive Names Use clear, descriptive names for tokens (e.g., “Production API”, “CI/CD Pipeline”).
Secure Storage Store tokens in environment variables or secure vaults, never in code repositories.
Regular Rotation Rotate tokens periodically for enhanced security.
Revoke Unused Delete tokens that are no longer needed to minimize security risks.
Token Validation
Validate token creation requests:
import vine from '@vinejs/vine'
export const createTokenValidator = vine . compile (
vine . object ({
name: vine . string (). trim (). minLength ( 1 ). maxLength ( 255 ). optional (),
})
)
When a token is created, the response includes:
{
"type" : "bearer" ,
"token" : "oat_MjE.YourActualTokenValueHere..."
}
Tokens are prefixed with oat_ (Opaque Access Token) followed by the user ID and the token hash.
Error Handling
Handle authentication errors in API requests:
try {
const response = await fetch ( '/api/endpoint' , {
headers: {
'Authorization' : `Bearer ${ token } `
}
})
if ( response . status === 401 ) {
// Token is invalid or expired
console . error ( 'Authentication failed' )
}
} catch ( error ) {
console . error ( 'Request failed:' , error )
}
Production Ready : The token system is secure and ready for production use with proper hashing, authorization, and revocation.