Controlling who can access your content—and what they can do with it—sits at the heart of every secure application. For developers building with Strapi, getting authentication and authorization right means the difference between a robust system and one that's vulnerable to unauthorized access.
This guide walks through practical implementation of Strapi authentication and authorization, from local login systems to JWT token management and role-based access control. Whether you're setting up your first Strapi project or tightening security on an existing application, you'll find the concepts and configurations needed to protect your content and APIs effectively.
In brief:
- Strapi supports multiple authentication methods including local credentials, JWT tokens, API tokens, and third-party OAuth 2.0 providers with PKCE support.
- The built-in RBAC system provides granular control over user permissions using a Resource:Action pattern (e.g., articles:read, articles:write, articles:delete) at both role and content-type levels.
- JWT tokens enable stateless authentication with mandatory 15-minute access token expiration and configurable refresh token rotation.
- Proper security implementation requires multiple overlapping controls including strong signing algorithms, server-side token validation, and secure storage in HttpOnly cookies.
What Are Authentication and Authorization in Strapi?
Authentication and authorization serve distinct but complementary functions in securing your Strapi application.
Authentication answers "Who are you?" It verifies a user's identity through credentials—whether that's an email and password, a JWT token, or OAuth credentials from a third-party provider.
Authorization answers "What can you do?" Once Strapi knows who you are, authorization determines which actions you're permitted to perform by checking roles, permissions, and policies.
| Aspect | Authentication | Authorization |
|---|---|---|
| Purpose | Verify identity | Control access |
| Strapi Implementation | JWT tokens, API tokens, OAuth | RBAC, permissions, policies |
| Failure Response | 401 Unauthorized | 403 Forbidden |
In practice, both work together. A request first passes through authentication—Strapi validates the token or credentials. Only after successful authentication does authorization kick in, checking whether the authenticated user has permission to perform the requested action.
Strapi Authentication Methods
Strapi provides multiple authentication approaches, each suited to different use cases.
Local Authentication with Email and Password
Local authentication is the most straightforward approach. Users register with an email and password, then log in using those credentials through built-in endpoints in the Users & Permissions plugin.
// Login request
const response = await fetch('http://localhost:1337/api/auth/local', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: 'user@example.com',
password: 'securePassword123'
})
});
const { jwt, user } = await response.json();For implementation details, consult the Strapi documentation.
JWT Token Authentication
Strapi uses JSON Web Tokens for stateless authentication. When a user logs in successfully, Strapi generates a JWT containing encoded user information and an expiration timestamp.
Understanding JWT Structure: A JWT consists of three Base64URL-encoded sections separated by periods: Header.Payload.Signature. The header specifies the token type and signing algorithm (e.g., HS256). The payload contains claims including the user ID, issued-at timestamp (iat), and expiration time (exp). The signature ensures the token hasn't been tampered with by cryptographically signing the header and payload.
When Strapi receives a request with a JWT, it validates the signature using the secret key defined in your configuration, checks that the token hasn't expired, and extracts the user information from the payload. This validation happens on every authenticated request.
According to the OWASP JSON Web Token Cheat Sheet, access tokens should have short expiration times—15 minutes is the commonly recommended duration. This limits the window during which a stolen token remains valid.
You can configure token expiration in your Strapi 5 project's plugin configuration:
// config/plugins.js
module.exports = {
'users-permissions': {
config: {
jwt: {
expiresIn: '15m', // 15-minute expiration
},
},
},
};Critical storage requirement: Store JWTs in HTTP-only cookies rather than localStorage. localStorage is accessible to any JavaScript code running on the page, making it vulnerable to XSS attacks.
Include the token in subsequent API requests via the Authorization header:
const response = await fetch('http://localhost:1337/api/articles', {
headers: {
'Authorization': `Bearer ${jwt}`
}
});API Token Authentication
API tokens provide an alternative authentication method for server-to-server communication and automated processes. Unlike JWTs tied to user sessions, API tokens are generated through the Strapi admin panel and can have extended lifetimes.
Strapi offers three types of API tokens:
- Read-only: Can only perform GET requests to fetch content
- Full-access: Can perform all CRUD operations across all content types
- Custom: Granular control over specific actions and content types
| Token Type | Best For | Expiration |
|---|---|---|
| JWT | User sessions, SPAs | Short (15 min) |
| API Token | Server-to-server, internal APIs | Configurable |
To generate an API token with specific permissions:
- Navigate to Settings > API Tokens in the admin panel.
- Click Create new API Token.
- Enter a name and description for the token.
- Select the token type (Read-only, Full-access, or Custom).
- For Custom tokens, configure permissions for each content type and action.
- Set the token duration (7 days, 30 days, 90 days, or unlimited).
- Click Save and securely store the generated token.
Token management best practices: Regenerate tokens periodically, especially for production environments. To revoke a compromised token, delete it from the admin panel—this immediately invalidates all requests using that token. Consider using the Strapi Marketplace for additional token management plugins.
Third-Party OAuth Providers
Strapi supports authentication through providers like Google, GitHub, and Auth0. Configure providers in Settings > Users & Permissions > Providers.
Step-by-step Google OAuth configuration:
- Create a project in the Google Cloud Console and enable the Google+ API.
- Navigate to Credentials and create an OAuth 2.0 Client ID.
- Set the authorized redirect URI to
http://your-domain/api/connect/google/callback. - Copy the Client ID and Client Secret.
- In Strapi, go to Settings > Users & Permissions > Providers.
- Enable Google and paste your credentials.
- Set the redirect URL to your frontend callback handler.
Common pitfall: Redirect URI mismatch errors occur when the callback URL in Google Console doesn't exactly match Strapi's expected format. Ensure you include the full path including /api/connect/google/callback.
For integrations with Auth0, implement the OAuth 2.0 authorization code flow with PKCE as recommended by current security standards. The PKCE extension prevents authorization code interception attacks, which is particularly important for single-page applications.
How Authorization Works in Strapi
Once a user is authenticated, authorization determines what actions they can perform through Role-Based Access Control (RBAC).
Role-Based Access Control (RBAC) Explained
RBAC works by associating users with roles, and roles with permissions. According to the NIST Role-Based Access Control Project, RBAC consists of four core components: users, roles, permissions, and sessions.
Default Roles: Public, Authenticated, and Custom
| Role | Description | Typical Permissions |
|---|---|---|
| Public | Unauthenticated users | Read-only access to published content |
| Authenticated | Logged-in users | CRUD operations on user-owned content |
| Custom | Defined by administrators | Whatever permissions you configure |
Custom roles address scenarios where default roles don't fit. Following ISO/IEC 29146:2024, roles should aggregate permissions into coherent sets based on job functions.
Configuring Permissions in the Admin Panel
- Navigate to Settings > Roles & Permissions.
- Select an existing role or create a new one.
- For each content type, toggle permissions: create, read, update, delete.
- Save the changes.
Permissions are granular—you can allow reading articles but not comments or permit creating entries but not deleting them.
Setting Up User Permissions in Strapi 5
Beyond basic role configuration, Strapi 5 provides advanced permission controls including custom roles and policies.
Creating Custom Roles
To create a custom role:
- Go to Settings > Roles & Permissions.
- Click Add new role.
- Name the role and configure permissions for each content type.
- Save the role.
The recommended approach uses a Resource:Action permission pattern like articles:read and articles:write.
Practical example: Consider an "Editor" role for a content team. Editors should create and update articles but not delete them (deletion requires manager approval). Configure this by enabling create and update permissions for the Article content type while leaving delete disabled.
Editors inherit the ability to read all published content from the base Authenticated role, demonstrating how permissions stack—users receive the combined permissions of all their assigned roles.
Using Custom Policies for Advanced Access Control
For scenarios requiring logic beyond role-based checks, custom policies provide programmatic access control:
// path: ./src/policies/isOwner.js
module.exports = async (policyContext, config, { strapi }) => {
const { id } = policyContext.params;
const user = policyContext.state.user;
if (!user) return false;
const article = await strapi.documents('api::article.article').findOne({
documentId: id,
populate: ['author'],
});
return article?.author?.id === user.id;
};Additional policy example—time-based access:
// path: ./src/policies/businessHoursOnly.js
module.exports = async (policyContext, config, { strapi }) => {
const now = new Date();
const hour = now.getUTCHours();
// Allow access only between 9 AM and 6 PM UTC
if (hour < 9 || hour >= 18) {
return false;
}
return true;
};Applying multiple policies to a single route:
// path: ./src/api/article/routes/article.js
module.exports = {
routes: [
{
method: 'PUT',
path: '/articles/:id',
handler: 'article.update',
config: {
policies: ['global::is-authenticated', 'isOwner', 'businessHoursOnly'],
},
},
],
};Policies execute in order—if any policy returns false, the request is denied. For more advanced implementations, consult the Strapi documentation on policies and permissions, or explore the Strapi blog for detailed tutorials.
Securing Your Strapi API
Proper API security extends beyond authentication and authorization to include rate limiting, HTTPS enforcement, and proper CORS configuration.
Protecting Routes with JWT and Policies
Combine JWT authentication with policies to secure API routes:
{
method: 'DELETE',
path: '/articles/:id',
handler: 'article.delete',
config: {
policies: ['global::is-authenticated', 'isOwner'],
},
}Implementing HTTPS and Rate Limiting
HTTPS should be considered essential for any production deployment. According to MDN Web Docs on Transport Layer Security, use TLS 1.2 as the minimum acceptable version, with TLS 1.3 preferred. Implement HSTS headers:
Strict-Transport-Security: max-age=31536000; includeSubDomainsRate limiting prevents brute-force attacks. Configure rate limiting in Strapi using middleware:
// config/middlewares.js
module.exports = [
'strapi::errors',
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
},
},
},
{
name: 'strapi::rateLimit',
config: {
interval: 60000, // 1 minute
max: 100, // limit each IP to 100 requests per interval
},
},
// ... other middlewares
];Implement tiered limits: 4 requests per minute for login endpoints, with higher limits for general API access based on authentication level.
Securing the Admin Panel
- Enforce NIST-compliant password requirements (minimum 8 characters, no forced complexity).
- Consider implementing MFA using phishing-resistant methods such as FIDO2/WebAuthn or TOTP through OAuth 2.0 providers like Auth0.
- Store admin session tokens in HTTP-Only cookies with Secure and SameSite=Strict attributes.
- Keep your Strapi installation current with security patches.
Field-level permissions are available in Strapi Enterprise for even finer control over data access, along with SSO for admin panel access and audit logs.
Best Practices for Strapi Authentication
Following security best practices helps protect your application from common vulnerabilities and ensures a reliable authentication experience for your users.
Security Best Practices Checklist
- Store JWTs in HTTP-only cookies with Secure and SameSite=Strict attributes.
- Use environment variables for secrets—never hardcode credentials.
- Set access token expiration to 15 minutes maximum.
- Implement refresh token rotation—issue new tokens on each refresh.
- Conduct regular permission audits.
- Enable MFA for admin users.
- Validate all tokens server-side on every request.
- Log authentication events for security monitoring.
Common Authentication Mistakes to Avoid
Even experienced developers can fall into these common traps that compromise application security. Here's what to watch out for and why each matters.
- Storing tokens in localStorage: XSS attacks can trivially extract tokens from browser storage.
- Overly permissive Public role: Review what endpoints are accessible without authentication.
- Skipping server-side validation: Client-side checks are easily bypassed.
- Hardcoding credentials: Use environment variables or secrets management systems.
- Ignoring token expiration: Never-expiring tokens create indefinite access windows and significant security vulnerabilities.
Strapi Authentication with Frontend Frameworks
Integrating Strapi authentication with your frontend requires handling token storage, protected routes, and session management. Here's how to implement secure authentication patterns with popular frameworks.
Next.js Authentication with Strapi
For JWT authentication in Next.js, consider using Auth.js (NextAuth.js v5), which provides native App Router support and can integrate with Strapi backends.
Auth.js configuration with Strapi:
// auth.ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
CredentialsProvider({
name: 'Strapi',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const res = await fetch(`${process.env.STRAPI_URL}/api/auth/local`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: credentials.email,
password: credentials.password,
}),
});
const data = await res.json();
if (data.jwt) {
return { ...data.user, jwt: data.jwt };
}
return null;
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) token.jwt = user.jwt;
return token;
},
},
});Key considerations:
- Validate tokens in Server Components via Data Access Layer before data fetching.
- Implement middleware for route optimization only, not as security boundary.
- Store refresh tokens in HTTP-only cookies.
- Use the token refresh flow by checking token expiration in Server Components and requesting new tokens from Strapi before they expire.
React Authentication with Strapi
For React SPAs, refer to Strapi authentication with React:
Protected route component example:
// components/ProtectedRoute.jsx
import { useAuth } from '../contexts/AuthContext';
import { Navigate } from 'react-router-dom';
export function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) return <div>Loading...</div>;
if (!user) return <Navigate to="/login" replace />;
return children;
}Auth context pattern for managing authentication state:
// contexts/AuthContext.jsx
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [accessToken, setAccessToken] = useState(null); // Store in memory only
const login = async (email, password) => {
const res = await fetch('/api/auth/local', { /* ... */ });
const data = await res.json();
setAccessToken(data.jwt); // Memory only, not localStorage
setUser(data.user);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}Key practices:
- Store access tokens in memory only.
- Use HTTP-only cookies for refresh tokens.
- Implement protected route components.
- Validate tokens server-side before processing requests.
Vue/Nuxt Authentication with Strapi
Nuxt.js offers the @nuxtjs/strapi module for streamlined integration:
useStrapiAuth usage example:
// pages/dashboard.vue
<script setup>
const { login, logout, user } = useStrapiAuth();
const handleLogin = async () => {
await login({
identifier: email.value,
password: password.value,
});
navigateTo('/dashboard');
};
</script>Middleware setup for protected routes:
// middleware/auth.js
export default defineNuxtRouteMiddleware((to) => {
const { user } = useStrapiAuth();
if (!user.value && to.path !== '/login') {
return navigateTo('/login');
}
});Key practices:
- Use built-in
useStrapiAuthcomposable for login/logout. - Implement route middleware for protected pages.
- Leverage server-side authentication checks.
For getting started quickly, explore Strapi starters that include pre-configured authentication setups for various frontend frameworks.
Troubleshooting Common Authentication Issues
When authentication doesn't work as expected, error messages can help pinpoint the problem. Here's how to diagnose and resolve the most frequent issues.
"401 Unauthorized" Errors
A 401 response means Strapi couldn't authenticate the user. Here's what typically causes this:
- Missing or malformed Authorization header (correct format:
Bearer <token>with space). - Expired token—check the
expclaim. - Invalid token signature or wrong API URL.
On the client side, check: request headers in DevTools, decode the JWT at jwt.io, test with Postman, and verify against Strapi server logs.
"403 Forbidden" Errors
A 403 means authentication succeeded but authorization failed. In practice, the most common issues are:
- Insufficient role permissions for the endpoint.
- Content type not exposed for the user's role.
- Custom policy rejection based on business logic.
Check the user's role permissions in Settings > Roles & Permissions and verify the content type includes the user's role.
Token Refresh Failures
When refresh token operations fail, users get unexpectedly logged out. Common causes include:
- Refresh token has expired (typically after 7-30 days).
- Token was revoked due to detected reuse (security measure).
- Server-side token storage mismatch after deployment.
- Clock skew between client and server affecting timestamp validation.
Debugging commands and tools:
# Decode JWT to inspect claims (header and payload)
echo "YOUR_JWT_TOKEN" | cut -d'.' -f2 | base64 -d | jq
# Check Strapi logs for authentication errors
strapi console
# Then: strapi.log.debug('auth')Quick reference for error codes:
| Error Code | Meaning | Common Fix |
|---|---|---|
| 401 | Not authenticated | Check token presence and format |
| 403 | Not authorized | Review role permissions |
| 419 | Session expired | Implement token refresh |
| 429 | Rate limited | Reduce request frequency |
Next Steps
Implementing robust authentication requires understanding both Strapi's mechanisms and security principles. Start with local authentication, add JWT token management with proper storage and expiration, and layer in RBAC to control access.
The combination of Strapi's built-in security features with industry best practices—including 15-minute access tokens, HTTP-only cookie storage, and refresh token rotation—creates a solid foundation for protecting your content and APIs. Regular audits, keeping Strapi updated, and monitoring authentication events help maintain security over time.
Ready to get started? Explore Strapi Cloud for managed deployments, check out use cases from other developers, or join the Strapi community to connect with other builders.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.