Authentication Flow
Complete guide to authentication in CodeNotify.
Overview
CodeNotify uses JWT (JSON Web Tokens) for stateless authentication with a refresh token mechanism.
Token Types
Access Token
- Validity: 15 minutes
- Purpose: Authenticate API requests
- Storage: Memory or secure storage
- Refresh: Use refresh token when expired
Refresh Token
- Validity: 7 days
- Purpose: Obtain new access tokens
- Storage: Secure httpOnly cookie (recommended)
- Rotation: Same token returned on refresh
Authentication Endpoints
1. Sign Up
Endpoint: POST /auth/signup
Request:
{
"email": "user@example.com",
"password": "SecurePass123!",
"name": "John Doe",
"phoneNumber": "+1234567890"
}Response:
{
"user": {
"id": "507f1f77bcf86cd799439011",
"email": "user@example.com",
"name": "John Doe",
"role": "user"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Process:
- Validate input (email format, password strength)
- Check if email already exists
- Hash password with bcrypt
- Create user in database (isEmailVerified: false)
- Generate OTP and send email
- Return user data (no tokens yet)
2. Verify Email
Endpoint: POST /auth/verify-email
Request:
{
"email": "user@example.com",
"otp": "123456"
}Process:
- Validate OTP
- Set isEmailVerified = true
- Return user data
- User can now sign in
3. Sign In
Endpoint: POST /auth/signin
Request:
{
"email": "user@example.com",
"password": "SecurePass123!"
}Response: Same as signup
Process:
- Find user by email
- Verify password with bcrypt
- Check if account is active AND email is verified
- Generate new tokens
- Update lastLogin timestamp
- Return user data and tokens
3. Refresh Token
Endpoint: POST /auth/refresh
Request:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Process:
- Verify refresh token signature
- Check if token is expired
- Find user by ID from token
- Verify stored refresh token matches
- Generate NEW access token
- Return SAME refresh token
Important: Refresh token is NOT regenerated!
4. Sign Out
Endpoint: POST /auth/signout
Headers: Authorization: Bearer <access_token>
Response:
{
"message": "Signed out successfully"
}Process:
- Verify access token
- Remove refresh token from database
- Return success message
Token Structure
Access Token Payload
{
"sub": "507f1f77bcf86cd799439011", // User ID
"email": "user@example.com",
"role": "user",
"iat": 1707998400, // Issued at
"exp": 1707999300 // Expires at (15 min)
}Refresh Token Payload
{
"sub": "507f1f77bcf86cd799439011", // User ID
"iat": 1707998400, // Issued at
"exp": 1708603200 // Expires at (7 days)
}Authentication Flow
Initial Authentication
1. User → POST /auth/signup or /auth/signin
2. Server → Validate credentials
3. Server → Generate tokens
4. Server → Store refresh token in DB
5. Server → Return { user, accessToken, refreshToken }
6. Client → Store tokens securelyMaking Authenticated Requests
1. Client → GET /users/profile
Headers: Authorization: Bearer <accessToken>
2. Server → Verify JWT signature
3. Server → Check expiration
4. Server → Extract user from token
5. Server → Process request
6. Server → Return responseToken Refresh Flow
1. Client → Request fails with 401 Unauthorized
2. Client → POST /auth/refresh with refreshToken
3. Server → Verify refresh token
4. Server → Generate new access token
5. Server → Return { accessToken, refreshToken }
6. Client → Retry original request with new tokenImplementation
Password Hashing
import * as bcrypt from 'bcrypt';
// Hash password
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Verify password
const isMatch = await bcrypt.compare(password, hashedPassword);JWT Generation
import { JwtService } from '@nestjs/jwt';
// Generate access token
const accessToken = this.jwtService.sign(
{ sub: user.id, email: user.email, role: user.role },
{ secret: process.env.JWT_SECRET, expiresIn: '15m' }
);
// Generate refresh token
const refreshToken = this.jwtService.sign(
{ sub: user.id },
{ secret: process.env.JWT_REFRESH_SECRET, expiresIn: '7d' }
);JWT Verification
// Verify access token
const payload = this.jwtService.verify(token, {
secret: process.env.JWT_SECRET
});
// Verify refresh token
const payload = this.jwtService.verify(token, {
secret: process.env.JWT_REFRESH_SECRET
});Guards
JwtAuthGuard
Protects routes requiring authentication:
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@CurrentUser() user: UserDocument) {
return user;
}RolesGuard
Protects routes requiring specific roles:
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
deleteUser(@Param('id') id: string) {
// Only admins can access
}Security Best Practices
Token Storage
✅ Recommended:
- Access token: Memory or sessionStorage
- Refresh token: Secure httpOnly cookie
❌ Not Recommended:
- localStorage (XSS vulnerable)
- Regular cookies without httpOnly
Token Secrets
# Use strong, random secrets (min 32 characters)
JWT_SECRET=your-super-secret-jwt-key-min-32-characters-long
JWT_REFRESH_SECRET=different-secret-for-refresh-tokens-min-32-charsRequirements:
- Minimum 32 characters
- Random and unpredictable
- Different for access and refresh
- Never commit to version control
- Rotate periodically
Password Requirements
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character
Rate Limiting
@Throttle(5, 60) // 5 requests per 60 seconds
@Post('signin')
async signin(@Body() dto: SignInDto) {
// Protected from brute force
}Error Handling
401 Unauthorized
Token is invalid or expired:
{
"statusCode": 401,
"message": "Unauthorized"
}Solution: Use refresh token to get new access token
403 Forbidden
User doesn't have required role:
{
"statusCode": 403,
"message": "Forbidden resource"
}Solution: Contact admin for role upgrade
Client Implementation
JavaScript Example
class AuthClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.accessToken = null;
this.refreshToken = null;
}
async signup(email, password, name) {
const response = await fetch(`${this.baseUrl}/auth/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, name })
});
const data = await response.json();
this.accessToken = data.accessToken;
this.refreshToken = data.refreshToken;
return data;
}
async makeAuthenticatedRequest(url, options = {}) {
// Try with current access token
let response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
// If unauthorized, refresh and retry
if (response.status === 401) {
await this.refreshAccessToken();
response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
}
return response.json();
}
async refreshAccessToken() {
const response = await fetch(`${this.baseUrl}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: this.refreshToken })
});
const data = await response.json();
this.accessToken = data.accessToken;
// refreshToken stays the same
}
async signout() {
await fetch(`${this.baseUrl}/auth/signout`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${this.accessToken}` }
});
this.accessToken = null;
this.refreshToken = null;
}
}Related Documentation
- API Authentication - Complete API reference
- User Management
- Security Guards

