-
-
Notifications
You must be signed in to change notification settings - Fork 0
Protecting API Endpoints
TokenAuthority provides the TokenAuthentication concern for validating JWT access tokens in your API controllers.
Include the concern to automatically validate access tokens on every request:
class Api::V1::ResourcesController < ActionController::API
include TokenAuthority::TokenAuthentication
def index
render json: token_user.resources
end
def show
resource = token_user.resources.find(params[:id])
render json: resource
end
endThe concern provides two methods for accessing token data:
| Method | Description |
|---|---|
token_user |
Returns the authenticated user associated with the access token |
token_scope |
Returns an array of scope tokens (e.g., ["read", "write"]), or [] if no scopes |
When you include TokenAuthentication, a before_action automatically validates the access token on every request. The validation process:
Extracts the Bearer token from the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Decodes the JWT and validates:
- The signature matches using your configured
secret_key - The token has not expired (
expclaim) - The audience matches your configured
audience_url(audclaim) - The issuer matches your configured
issuer_url(issclaim)
Checks that the token's associated session is still active:
- The session exists in the database
- The session has not been revoked
- The session has not been refreshed (only the latest token is valid)
If validation fails, the concern automatically renders an appropriate JSON error response.
The concern automatically handles authentication errors and returns appropriate JSON responses.
| Scenario | HTTP Status | Error Key |
|---|---|---|
Missing or blank Authorization header |
401 Unauthorized | missing_auth_header |
| Malformed or invalid JWT | 401 Unauthorized | invalid_token |
| Expired token or inactive session | 401 Unauthorized | unauthorized_token |
Missing Authorization Header:
{
"error": "Authorization header not found or value is blank"
}Invalid Token:
{
"error": "The access token is invalid or malformed"
}Expired or Revoked Token:
{
"error": "The access token is expired or unauthorized"
}If you need custom error handling, override the response methods:
class Api::V1::BaseController < ActionController::API
include TokenAuthority::TokenAuthentication
private
def missing_auth_header_response
render json: { code: "auth_required", message: "Please provide an access token" }, status: :unauthorized
end
def invalid_token_response
render json: { code: "invalid_token", message: "The provided token is invalid" }, status: :unauthorized
end
def unauthorized_token_response
render json: { code: "token_expired", message: "Your session has expired" }, status: :unauthorized
end
endFor endpoints that work with or without authentication, rescue from the authentication errors:
class Api::V1::ArticlesController < ActionController::API
include TokenAuthority::TokenAuthentication
# Skip automatic validation for specific actions
skip_before_action :decode_token, only: [:index]
def index
# Manually attempt authentication
if authenticated?
render json: token_user.personalized_articles
else
render json: Article.public_articles
end
end
def show
# This action still requires authentication
render json: token_user.articles.find(params[:id])
end
private
def authenticated?
decode_token
true
rescue TokenAuthority::MissingAuthorizationHeaderError,
TokenAuthority::InvalidAccessTokenError,
TokenAuthority::UnauthorizedAccessTokenError
false
end
endUse token_scope to check that the token includes required scopes:
class Api::V1::AdminController < ActionController::API
include TokenAuthority::TokenAuthentication
before_action :require_admin_scope
def dashboard
render json: { stats: Admin.dashboard_stats }
end
private
def require_admin_scope
unless token_scope.include?("admin")
render json: { error: "Insufficient scope" }, status: :forbidden
end
end
endFor more complex scope requirements, consider a helper module:
module ScopeAuthorization
extend ActiveSupport::Concern
private
def require_scope(*required_scopes)
missing = required_scopes - token_scope
if missing.any?
render json: {
error: "insufficient_scope",
required: required_scopes,
granted: token_scope
}, status: :forbidden
end
end
end
class Api::V1::DocumentsController < ActionController::API
include TokenAuthority::TokenAuthentication
include ScopeAuthorization
def index
require_scope("read")
render json: token_user.documents
end
def create
require_scope("write")
document = token_user.documents.create!(document_params)
render json: document, status: :created
end
def destroy
require_scope("delete")
token_user.documents.find(params[:id]).destroy
head :no_content
end
endAccess tokens include the following JWT claims:
| Claim | Description |
|---|---|
sub |
Subject - the user's ID |
aud |
Audience - your configured audience_url (or resource URIs if RFC 8707 is used) |
iss |
Issuer - your configured issuer_url
|
exp |
Expiration time |
iat |
Issued at time |
jti |
JWT ID - unique token identifier |
scope |
Space-delimited scope tokens (only present if scopes were requested) |
- Configuration Reference - Token settings
- User Authentication - Web authentication for consent screens
Getting Started
- Installation Guide
- MCP Quickstart
- Configuration Reference
- User Authentication
- Protecting API Endpoints
- Customizing Views
- Event Logging
- Instrumentation
Process Flows
- Authorization Code Grant
- Authorization Code Redemption
- Token Refresh
- Token Revocation
- Authorization Server Metadata
- Protected Resource Metadata
- Dynamic Client Registration
- Client Metadata Documents
Development