package auth import ( "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) // AccessTokenClaims represents the claims stored in a short-lived access token. // Access tokens are used for authenticating API requests and contain user identity // and authorization information. They are stateless and validated purely through // JWT signature verification without requiring database lookups. // // Typical lifetime: 15 minutes to 1 hour // Use case: Included in Authorization header for every API request type AccessTokenClaims struct { UserID uuid.UUID `json:"user_id"` // Unique identifier of the authenticated user TenantID uuid.UUID `json:"tenant_id"` // Tenant/organization ID for multi-tenant isolation Email string `json:"email"` // User's email address for identification Role string `json:"role"` // User's role (e.g., "admin", "user", "contractor") for authorization TokenType string `json:"token_type"` // Always "access" - used to prevent token type confusion attacks jwt.RegisteredClaims // Standard JWT claims (exp, iat, nbf, iss, sub) } // RefreshTokenClaims represents the claims stored in a long-lived refresh token. // Refresh tokens are used to obtain new access tokens without requiring the user // to re-authenticate. They are stateful and validated against database sessions, // allowing for instant revocation when needed (logout, security events, etc.). // // Typical lifetime: 7-30 days // Use case: Stored securely on client, exchanged for new access tokens when they expire // // Security model: Hybrid approach combining JWT signature validation with database // session validation for both performance and revocability. type RefreshTokenClaims struct { UserID uuid.UUID `json:"user_id"` // Unique identifier of the authenticated user SessionID uuid.UUID `json:"session_id"` // Database session ID for revocation and tracking TokenID string `json:"token_id"` // Cryptographically random token (32 bytes) embedded in JWT and hashed in database TokenType string `json:"token_type"` // Always "refresh" - used to prevent token type confusion attacks jwt.RegisteredClaims // Standard JWT claims (exp, iat, nbf, iss, sub) } // Valid validates the AccessTokenClaims by checking the token type. // This method is called automatically by the JWT library during token parsing // to perform custom validation logic beyond the standard claims validation. // // Note: In jwt-go v5, validation of standard claims (exp, iat, nbf) is handled // through parser options like jwt.WithExpirationRequired() rather than the Valid() // method. This method only validates custom business logic (token type). // // Returns: // - nil if the token type is "access" // - jwt.ErrInvalidType if the token type is not "access" (prevents using refresh tokens as access tokens) func (c AccessTokenClaims) Valid() error { if c.TokenType != "access" { return jwt.ErrInvalidType } // Standard claims validation (exp, iat, nbf, etc.) is handled by parser options: // // Example usage: // token, err := jwt.ParseWithClaims( // tokenString, // &AccessTokenClaims{}, // keyFunc, // jwt.WithExpirationRequired(), // Validates exp claim // jwt.WithIssuedAt(), // Validates iat claim // jwt.WithTimeFunc(time.Now), // Provides time source for validation // ) // // This approach is more explicit and testable than the v4 nested validation. return nil } // Valid validates the RefreshTokenClaims by checking the token type. // This method is called automatically by the JWT library during token parsing // to perform custom validation logic beyond the standard claims validation. // // Note: Refresh tokens undergo additional validation beyond this method: // 1. JWT signature verification (ensures token was issued by our server) // 2. Standard claims validation via parser options (exp, iat, nbf) // 3. This method's token type check (prevents using access tokens as refresh tokens) // 4. Database session lookup using SessionID (ensures session still exists) // 5. Token hash verification (ensures TokenID matches hashed value in database) // 6. Session status checks (not revoked, not expired in database) // // This multi-layer validation provides defense in depth security. // // Returns: // - nil if the token type is "refresh" // - jwt.ErrInvalidType if the token type is not "refresh" (prevents using access tokens as refresh tokens) func (c RefreshTokenClaims) Valid() error { if c.TokenType != "refresh" { return jwt.ErrInvalidType } // Standard claims validation (exp, iat, nbf, etc.) is handled by parser options. // See AccessTokenClaims.Valid() comment for detailed explanation. return nil }