Authentication
Complete authentication system with email/password and OAuth providers
Fastly provides a complete authentication system with multiple sign-in methods, JWT-based sessions, and multi-device session tracking.
Authentication Methods
Email/Password
Traditional registration with email verification and password reset.
GitHub OAuth
One-click sign in with GitHub accounts.
Google OAuth
One-click sign in with Google accounts.
JWT Token System
The authentication system uses two JWT tokens with different lifetimes:
Prop
Type
Token Payload
interface TokenPayload {
userId: string;
email: string;
type: 'access' | 'refresh';
iat: number;
exp: number;
}Token Storage
Tokens are managed via the token manager at src/lib/config/token-manager.ts:
import { tokenManager } from '@/lib/config/token-manager';
// Store tokens after login
tokenManager.setTokens(accessToken, refreshToken, sessionId);
// Retrieve tokens
const accessToken = tokenManager.getAccessToken();
const refreshToken = tokenManager.getRefreshToken();
const sessionId = tokenManager.getSessionId();
// Clear tokens on logout
tokenManager.clearTokens();Tokens are stored in localStorage. The axios interceptor automatically attaches them to API requests.
Session Tracking
Every successful authentication creates a session record with device metadata:
Prop
Type
Session Validation
Every authenticated request validates:
- Access token is valid and not expired
- Session ID exists in the
X-Session-Idheader - Session is not revoked (
revokedAt === null) - Session belongs to the authenticated user
// Request headers for authenticated endpoints
{
'Authorization': 'Bearer <access-token>',
'X-Session-Id': '<session-id>'
}Authentication Flow
User initiates authentication
User chooses email/password or OAuth provider on the login page.
Credentials validated
For email/password: credentials checked against database. For OAuth: authorization code exchanged for user data.
Email verification check
For email/password users, email must be verified before login completes. OAuth users are automatically verified.
Tokens generated
Access and refresh tokens are created with user payload.
const tokens = generateTokenPair(userId, email);
// { accessToken: '...', refreshToken: '...' }Session created
A session record is created with device metadata from the request.
const session = await createUserSession({
userAuthId,
authMethod: 'email',
request,
});Response returned
Tokens, session ID, and user data are returned to the client.
{
"accessToken": "eyJhbG...",
"refreshToken": "eyJhbG...",
"session": {
"sessionId": "uuid-here",
"browser": "Chrome 120",
"os": "macOS 14"
},
"user": {
"userId": "...",
"email": "user@example.com",
"firstName": "John"
}
}Token Refresh
When the access token expires, the client automatically refreshes it:
// POST /api/auth/refresh-token
{
"refreshToken": "eyJhbG..."
}
// Headers
{
"X-Session-Id": "session-uuid"
}The axios interceptor handles this automatically. Failed refresh attempts redirect to login.
Frontend Hooks
useSession
Access current authentication state:
import { useSession } from '@/hooks/auth/useSession';
function Component() {
const { user, isAuthenticated, isLoading } = useSession();
if (isLoading) return <Loading />;
if (!isAuthenticated) return <Redirect to="/log-in" />;
return <div>Welcome, {user.firstName}</div>;
}useAuthMutations
Authentication actions with React Query:
import { useLogin, useLogout } from '@/hooks/auth/useAuthMutations';
function LoginForm() {
const login = useLogin();
const handleSubmit = (data) => {
login.mutate(data, {
onSuccess: () => router.push('/dashboard'),
});
};
}Available mutations:
useLogin()- Email/password loginuseCreateAccount()- RegistrationuseVerifyEmail()- OTP verificationuseResendVerification()- Resend OTPuseForgotPassword()- Request resetuseResetPassword()- Complete resetuseLogout()- End session
Route Protection
Client-side
Use the useRequireAuth hook:
import { useRequireAuth } from '@/hooks/auth/useRequireAuth';
function ProtectedPage() {
useRequireAuth(); // Redirects to login if not authenticated
return <Dashboard />;
}Server-side
Use the authenticate middleware in API routes:
import { authenticate } from '@/lib/auth/auth-middleware';
export async function GET(request: Request) {
const auth = await authenticate(request);
if (!auth.success) {
return auth.response; // Returns 401
}
const { user } = auth;
// user.userId, user.email available
}Authentication Pages
| Route | Purpose | File |
|---|---|---|
/create-account | New user registration | src/app/(auth)/create-account/page.tsx |
/log-in | User login | src/app/(auth)/log-in/page.tsx |
/email-verification | OTP code entry | src/app/(auth)/email-verification/page.tsx |
/forgot-password | Request password reset | src/app/(auth)/forgot-password/page.tsx |
/reset-password | Set new password | src/app/(auth)/reset-password/page.tsx |
/oauth-callback | Handle OAuth redirects | src/app/(auth)/oauth-callback/page.tsx |
Security Considerations
Tokens are stored in localStorage which is vulnerable to XSS attacks. For higher security requirements, consider httpOnly cookies.
Security features implemented:
- Passwords hashed with bcrypt (10 salt rounds)
- JWT secrets separate for access and refresh tokens
- Session validation on every authenticated request
- Session revocation capability
- IP and device tracking
- OTP expiration (10 minutes for email verification)
- Password reset token expiration (10 minutes)