Authentication and Authorization
Relevant source files
The following files were used as context for generating this wiki page:
- apps/backend/src/auth/auth.module.ts
- apps/backend/src/auth/auth.service.ts
- apps/backend/src/auth/auth.controller.ts
- apps/backend/src/auth/dto/auth.dto.ts
- apps/backend/src/users/entities/user.entity.ts
- apps/backend/src/users/guards/startnet.auth.guard.ts
- apps/backend/src/auth/strategies/jwt.strategy.ts
- apps/backend/README.md
The OnChain Sage backend employs a robust authentication system that integrates Starknet wallet-based signatures with traditional JSON Web Tokens (JWT) for securing API endpoints. This hybrid approach provides a seamless and cryptographically secure way for users to authenticate using their blockchain wallets, which then grants them a standard JWT for subsequent API requests.
The system is designed to be modular, leveraging NestJS providers, guards, and strategies to handle different aspects of authentication and authorization. The core flow involves a challenge-response mechanism with a Starknet wallet, followed by the issuance of a JWT that is used to access protected routes.
Architecture Overview
The authentication system is encapsulated within the AuthModule. It orchestrates the interaction between controllers, services, and strategies to manage the end-to-end authentication process.
The following diagram illustrates the main components of the authentication and authorization system.
This diagram shows how the AuthModule wires together the AuthController for handling HTTP requests, the AuthService for business logic, and the JwtStrategy for validating tokens.
Sources: apps/backend/src/auth/auth.module.ts, apps/backend/src/auth/auth.service.ts, apps/backend/src/auth/auth.controller.ts
Core Components
| Component | Description | Source File |
|---|---|---|
AuthModule | A NestJS module that encapsulates all authentication-related logic, including JWT configuration and providers. | apps/backend/src/auth/auth.module.ts |
AuthController | Exposes REST API endpoints for the authentication flow, such as generating a challenge and verifying a signature. | apps/backend/src/auth/auth.controller.ts |
AuthService | Contains the core business logic for the challenge-response mechanism, signature verification, and JWT generation. | apps/backend/src/auth/auth.service.ts |
JwtStrategy | A Passport.js strategy for validating JWTs included in API requests to protected endpoints. | apps/backend/src/auth/strategies/jwt.strategy.ts |
JwtAuthGuard | A NestJS guard that uses the JwtStrategy to protect routes, ensuring only authenticated users can access them. | apps/backend/src/auth/guards/jwt-auth.guard.ts |
StarknetAuthGuard | An alternative guard that can protect routes by verifying a Starknet signature directly from the Authorization header. | apps/backend/src/users/guards/startnet.auth.guard.ts |
User Entity | The TypeORM entity representing a user, which stores the wallet address and authentication-related timestamps. | apps/backend/src/users/entities/user.entity.ts |
Starknet Wallet Authentication Flow
The primary authentication method is a challenge-response flow using a Starknet wallet. This ensures that the user is in control of the private key associated with their wallet address.
The sequence diagram below details this process.
1. Challenge Generation
- Endpoint:
POST /auth/starknet/challenge - The client sends the user's
walletAddress. - The
AuthService.generateChallengemethod is called. It finds or creates aUserrecord, generates a unique challenge string, and saves it to the user'slastChallengefield in the database along with a timestamp. - The challenge is returned to the client.
Sources: apps/backend/src/auth/auth.controller.ts:20-23, apps/backend/src/auth/auth.service.ts:21-38
2. Signature Verification
- Endpoint:
POST /auth/starknet/verify - The client prompts the user to sign the received challenge (
message) with their Starknet wallet and sends thewalletAddress,signature, and originalmessageto the backend. - The
AuthService.verifySignaturemethod retrieves the user and uses theverifyStarknetSignatureutility to cryptographically verify the signature against the wallet address and message. - If verification is successful, the user's
lastLoginAtandisVerifiedfields are updated. A JWT is generated and returned to the client. - If verification fails, a
401 Unauthorizedexception is thrown.
Sources: apps/backend/src/auth/auth.controller.ts:25-35, apps/backend/src/auth/auth.service.ts:40-97
Authentication DTOs
| DTO | Fields | Description | Source File |
|---|---|---|---|
StarknetChallengeDto | walletAddress: string | Used to request an authentication challenge. | apps/backend/src/auth/dto/auth.dto.ts |
StarknetVerifyDto | walletAddress: stringsignature: string[]message: stringmetadata?: object | Used to submit the signed challenge for verification. Optional metadata can be included to update user profile information. | apps/backend/src/auth/dto/auth.dto.ts |
StarknetAuthResponseDto | accessToken: stringuser: object | The response upon successful verification, containing the JWT and user details. | apps/backend/src/auth/dto/auth.dto.ts |
JWT Session Management
Once a user is authenticated via the Starknet flow, a JWT is issued. This token is used for authenticating subsequent requests to protected API endpoints, avoiding the need to sign a message for every request.
JWT Configuration
The AuthModule configures the JwtModule asynchronously. It retrieves the secret and expiration time from environment variables, with sensible defaults.
| Configuration | Environment Variable | Default | Description |
|---|---|---|---|
| Secret | JWT_SECRET | 'your-secret-key' | The secret key used to sign the JWTs. |
| Expiration | JWT_EXPIRATION | '24h' | The duration for which a JWT is valid. |
Sources: apps/backend/src/auth/auth.module.ts:12-21
JWT Strategy (JwtStrategy)
The JwtStrategy is responsible for validating the JWT sent by the client.
- It extends
PassportStrategyfrom@nestjs/passport. - It is configured to extract the JWT from the
Authorizationheader as a Bearer Token. - The
validatemethod receives the decoded JWT payload and is expected to return the user object, which NestJS then attaches to theRequestobject. The payload contains the userid(sub) andwalletAddress.
Sources: apps/backend/src/auth/strategies/jwt.strategy.ts, apps/backend/src/auth/auth.module.ts:24
Authorization and Guards
Guards are used to protect endpoints and enforce authorization rules.
JwtAuthGuard
This is the primary guard for protecting routes. When applied to a controller or route handler (e.g., with @UseGuards(JwtAuthGuard)), it automatically invokes the JwtStrategy to validate the Bearer Token in the request header. If the token is missing or invalid, it denies access.
Example usage on the /auth/profile endpoint:
// Source: apps/backend/src/auth/auth.controller.ts:37-41
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
StarknetAuthGuard
This guard provides an alternative authentication mechanism. Instead of a JWT, it expects a Base64-encoded token in the Authorization: Bearer <token> header containing the wallet address, signature, and message.
The guard's canActivate method performs the following steps:
- Decodes the token from the header.
- Verifies the Starknet signature using the
verifyStarknetMessageutility. - If the signature is valid, it finds the user by
walletAddress. - If the user does not exist, a new user is created and saved to the database.
- The user object is attached to the request.
This guard is useful for scenarios where a stateless, per-request signature verification is preferred over the JWT session-based approach.
Sources: apps/backend/src/users/guards/startnet.auth.guard.ts