Skip to content

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:

json
{
  "email": "user@example.com",
  "password": "SecurePass123!",
  "name": "John Doe",
  "phoneNumber": "+1234567890"
}

Response:

json
{
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "user@example.com",
    "name": "John Doe",
    "role": "user"
  },
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Process:

  1. Validate input (email format, password strength)
  2. Check if email already exists
  3. Hash password with bcrypt
  4. Create user in database (isEmailVerified: false)
  5. Generate OTP and send email
  6. Return user data (no tokens yet)

2. Verify Email ​

Endpoint: POST /auth/verify-email

Request:

json
{
  "email": "user@example.com",
  "otp": "123456"
}

Process:

  1. Validate OTP
  2. Set isEmailVerified = true
  3. Return user data
  4. User can now sign in

3. Sign In ​

Endpoint: POST /auth/signin

Request:

json
{
  "email": "user@example.com",
  "password": "SecurePass123!"
}

Response: Same as signup

Process:

  1. Find user by email
  2. Verify password with bcrypt
  3. Check if account is active AND email is verified
  4. Generate new tokens
  5. Update lastLogin timestamp
  6. Return user data and tokens

3. Refresh Token ​

Endpoint: POST /auth/refresh

Request:

json
{
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

json
{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Process:

  1. Verify refresh token signature
  2. Check if token is expired
  3. Find user by ID from token
  4. Verify stored refresh token matches
  5. Generate NEW access token
  6. Return SAME refresh token

Important: Refresh token is NOT regenerated!

4. Sign Out ​

Endpoint: POST /auth/signout

Headers: Authorization: Bearer <access_token>

Response:

json
{
  "message": "Signed out successfully"
}

Process:

  1. Verify access token
  2. Remove refresh token from database
  3. Return success message

Token Structure ​

Access Token Payload ​

json
{
  "sub": "507f1f77bcf86cd799439011",  // User ID
  "email": "user@example.com",
  "role": "user",
  "iat": 1707998400,                   // Issued at
  "exp": 1707999300                    // Expires at (15 min)
}

Refresh Token Payload ​

json
{
  "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 securely

Making 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 response

Token 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 token

Implementation ​

Password Hashing ​

typescript
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 ​

typescript
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 ​

typescript
// 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:

typescript
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@CurrentUser() user: UserDocument) {
  return user;
}

RolesGuard ​

Protects routes requiring specific roles:

typescript
@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 ​

bash
# 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-chars

Requirements:

  • 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 ​

typescript
@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:

json
{
  "statusCode": 401,
  "message": "Unauthorized"
}

Solution: Use refresh token to get new access token

403 Forbidden ​

User doesn't have required role:

json
{
  "statusCode": 403,
  "message": "Forbidden resource"
}

Solution: Contact admin for role upgrade

Client Implementation ​

JavaScript Example ​

javascript
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;
  }
}

Built with ❤️ for competitive programmers worldwide.