Skip to content

JWT Authentication ​

JSON Web Token (JWT) implementation for stateless authentication in CodeNotify.

Overview ​

CodeNotify uses JWT for secure, stateless authentication. Tokens are signed using HS256 algorithm and contain user identity and role information.

Token Types ​

Access Token ​

  • Expiry: 15 minutes
  • Purpose: API authentication
  • Storage: Client-side (memory or secure storage)
  • Secret: JWT_SECRET environment variable

Refresh Token ​

  • Expiry: 7 days
  • Purpose: Obtain new access tokens
  • Storage: Database (hashed) + Client-side
  • Secret: JWT_REFRESH_SECRET environment variable

Token Structure ​

Payload ​

typescript
interface JwtPayload {
  sub: string;      // User ID
  email: string;    // User email
  role: string;     // User role (user/admin)
  iat: number;      // Issued at timestamp
  exp: number;      // Expiration timestamp
}

Example Token ​

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiI2NTc4OTBhYmNkZWYxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwicm9sZSI6InVzZXIiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDAwMDkwMH0.
signature_here

Configuration ​

Environment Variables ​

bash
# JWT Secrets (use strong random strings)
JWT_SECRET=your-super-secret-key-min-32-chars
JWT_REFRESH_SECRET=your-refresh-secret-key-min-32-chars

# Token Expiry
JWT_EXPIRES_IN=15m
REFRESH_TOKEN_EXPIRES_IN=7d

NestJS Configuration ​

typescript
// auth.module.ts
JwtModule.registerAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    secret: configService.get<string>('JWT_SECRET'),
    signOptions: { 
      expiresIn: '15m',
      algorithm: 'HS256'
    },
  }),
  inject: [ConfigService],
})

Token Generation ​

Sign Up / Sign In ​

typescript
async generateTokens(user: UserDocument) {
  const payload: JwtPayload = {
    sub: user.id,
    email: user.email,
    role: user.role,
  };

  const accessToken = await this.jwtService.signAsync(payload, {
    secret: this.configService.get('JWT_SECRET'),
    expiresIn: '15m',
  });

  const refreshToken = await this.jwtService.signAsync(payload, {
    secret: this.configService.get('JWT_REFRESH_SECRET'),
    expiresIn: '7d',
  });

  return { accessToken, refreshToken };
}

Token Validation ​

JWT Strategy ​

typescript
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_SECRET'),
    });
  }

  async validate(payload: JwtPayload) {
    return {
      id: payload.sub,
      email: payload.email,
      role: payload.role,
    };
  }
}

Usage in Routes ​

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

Security Best Practices ​

✅ Do ​

  1. Use Strong Secrets: Minimum 32 characters, random
  2. Short Access Token Expiry: 15 minutes or less
  3. HTTPS Only: Never send tokens over HTTP
  4. Secure Storage: Use httpOnly cookies or secure storage
  5. Validate on Every Request: Check token signature and expiry
  6. Implement Refresh Tokens: Allow token renewal without re-login
  7. Hash Refresh Tokens: Store hashed in database

❌ Don't ​

  1. Don't Store Sensitive Data: Keep payload minimal
  2. Don't Use Weak Secrets: Avoid predictable strings
  3. Don't Share Secrets: Different secrets per environment
  4. Don't Ignore Expiry: Always check token expiration
  5. Don't Log Tokens: Never log full tokens
  6. Don't Use Long Expiry: Keep access tokens short-lived

Token Refresh Flow ​

mermaid
sequenceDiagram
    participant Client
    participant API
    participant AuthService
    participant Database
    
    Client->>API: Request with expired access token
    API-->>Client: 401 Unauthorized
    Client->>API: POST /auth/refresh<br/>{refreshToken}
    API->>AuthService: Validate refresh token
    AuthService->>Database: Verify hashed token
    Database-->>AuthService: Token valid
    AuthService->>AuthService: Generate new access token
    AuthService-->>Client: {accessToken (new)}
    Client->>API: Request with new access token
    API-->>Client: 200 OK

Error Handling ​

Common Errors ​

ErrorStatusCauseSolution
Invalid token401Malformed or tampered tokenRe-authenticate
Token expired401Access token expiredUse refresh token
Invalid signature401Wrong secret or tamperedRe-authenticate
Missing token401No Authorization headerProvide token

Error Response ​

json
{
  "statusCode": 401,
  "message": "Unauthorized",
  "error": "Invalid or expired token"
}

Testing ​

Unit Tests ​

typescript
describe('JWT Service', () => {
  it('should generate valid access token', async () => {
    const tokens = await authService.generateTokens(user);
    expect(tokens.accessToken).toBeDefined();
    
    const decoded = jwtService.decode(tokens.accessToken);
    expect(decoded.sub).toBe(user.id);
  });

  it('should reject expired token', async () => {
    const expiredToken = await jwtService.signAsync(payload, {
      expiresIn: '-1s'
    });
    
    await expect(
      jwtService.verifyAsync(expiredToken)
    ).rejects.toThrow();
  });
});

Built with ❤️ for competitive programmers worldwide.