aurganize-backend/backend/pkg/auth/claims.go

102 lines
4.8 KiB
Go

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
}