Skip to main content

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:
app/users/models/user.ts
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:
config/auth.ts
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:
app/users/routes.ts
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

1

Generate Token

Administrator creates a new token from the settings page, giving it a descriptive name.
2

Copy Token

The token value is displayed once. Copy and store it securely (e.g., in environment variables).
3

Use Token

Include the token in the Authorization header of API requests: Bearer YOUR_TOKEN.
4

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:
app/users/validators.ts
import vine from '@vinejs/vine'

export const createTokenValidator = vine.compile(
  vine.object({
    name: vine.string().trim().minLength(1).maxLength(255).optional(),
  })
)

API Response Format

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.

Build docs developers (and LLMs) love