Hybrid mode supports both stateless tokens AND cookie-based sessions for maximum flexibility.
Hybrid mode allows:
# config/packages/better_auth.yaml
better_auth:
mode: 'hybrid'
secret: '%env(BETTER_AUTH_SECRET)%'
session:
lifetime: 604800 # 7 days
cookie_name: 'better_auth_session'
token:
lifetime: 3600 # 1 hour
refresh_lifetime: 2592000 # 30 days
The backend accepts both authentication methods:
// AuthController.php
private function getAuthToken(Request $request): ?string
{
// 1. Try Bearer token (API mode)
$token = $this->getBearerToken($request);
if ($token) {
return $token;
}
// 2. Try cookie (Session mode)
$token = $request->cookies->get('access_token');
if ($token) {
return $token;
}
return null;
}
Clients choose how to authenticate:
API Style (Bearer Token):
// Store in memory/localStorage
localStorage.setItem('access_token', token);
// Send in header
headers: { 'Authorization': `Bearer ${token}` }
Session Style (Cookie):
// Store in cookie
setCookie('access_token', token, 1);
// Automatically sent with requests
fetch('/api/me', { credentials: 'include' });
Flow:
// Magic Link callback
const { access_token, refresh_token } = await magicLinkApi.verify(token);
setCookie('access_token', access_token, 1);
setCookie('refresh_token', refresh_token, 7);
Mobile (API):
// Store securely
SecureStore.setItemAsync('access_token', token);
// Send in header
headers: { 'Authorization': `Bearer ${token}` }
Web (Session):
// Store in cookie
document.cookie = `access_token=${token}`;
// Auto-sent with requests
fetch('/api/me'); // Works!
Public API:
Admin Dashboard:
const api = axios.create({
baseURL: 'http://localhost:8000',
withCredentials: true, // Important for cookies!
});
// Add token to header (optional, for API mode)
api.interceptors.request.use((config) => {
const token = getCookie('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Login can work with either method
async function login(email: string, password: string) {
const response = await api.post('/auth/login', { email, password });
const { access_token, refresh_token, user } = response.data;
// Choice: Store in cookies (session-like)
setCookie('access_token', access_token, 1);
setCookie('refresh_token', refresh_token, 7);
// OR: Store in memory/localStorage (API-like)
// localStorage.setItem('access_token', access_token);
return user;
}
For cross-origin requests with cookies:
# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(FRONTEND_URL)%']
allow_credentials: true # Required for cookies!
allow_headers: ['*']
allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
FRONTEND_URL=https://myapp.com
setCookie('access_token', $token, [
'expires' => time() + 86400,
'path' => '/',
'httponly' => true,
'samesite' => 'lax',
]);
setCookie('access_token', $token, [
'expires' => time() + 86400,
'path' => '/',
'httponly' => true,
'secure' => true, // HTTPS only
'samesite' => 'strict', // CSRF protection
]);
# Before
better_auth:
mode: 'api'
# After
better_auth:
mode: 'hybrid'
No breaking changes! API tokens continue to work, cookies now also supported.
# Before
better_auth:
mode: 'session'
# After
better_auth:
mode: 'hybrid'
No breaking changes! Sessions continue to work, API tokens now also supported.
| Scenario | Recommendation |
|---|---|
| SPA only | api mode |
| Traditional web only | session mode |
| Mobile + Web | hybrid mode |
| Magic Link auth | hybrid mode |
| Public API + Dashboard | hybrid mode |
| Unknown future needs | hybrid mode |
Recommendation: Use hybrid by default unless you have specific requirements.
How can I help you explore Laravel packages today?