Access Tokens
Token-based authentication is ideal for APIs where clients send a Bearer token with each request instead of relying on cookies and sessions. Nimbus's auth.Guard interface makes it straightforward to build a token guard.
Concept
API tokens are opaque strings generated on login or user creation. The client includes the token in the Authorization header as Bearer <token>. The server validates the token against a store (database, cache, etc.) and resolves the associated user.
Token model
Store tokens in a database table alongside the user ID and optional expiration:
type AccessToken struct {
database.Model
Token string `gorm:"uniqueIndex"`
UserID uint
ExpiresAt time.Time
}
Generating tokens
Generate a cryptographically secure token using crypto/rand and store it in the database:
func GenerateToken(db *gorm.DB, userID uint, ttl time.Duration) (string, error) {
b := make([]byte, 32)
rand.Read(b)
token := hex.EncodeToString(b)
record := AccessToken{
Token: token,
UserID: userID,
ExpiresAt: time.Now().Add(ttl),
}
if err := db.Create(&record).Error; err != nil {
return "", err
}
return token, nil
}
Implementing a token guard
Implement the auth.Guard interface to read the Bearer token from the request and look up the user:
type TokenGuard struct {
DB *gorm.DB
}
func (g *TokenGuard) User(ctx context.Context) (auth.User, error) {
req, _ := ctx.Value("http_request").(*http.Request)
header := req.Header.Get("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
return nil, nil
}
tokenStr := strings.TrimPrefix(header, "Bearer ")
var token AccessToken
err := g.DB.Where("token = ? AND expires_at > ?", tokenStr, time.Now()).First(&token).Error
if err != nil {
return nil, nil
}
var user AppUser
if err := g.DB.First(&user, token.UserID).Error; err != nil {
return nil, nil
}
return &user, nil
}
func (g *TokenGuard) Login(ctx context.Context, user auth.User) error {
return nil // tokens are created separately via GenerateToken
}
func (g *TokenGuard) Logout(ctx context.Context) error {
return nil // revoke by deleting the token record
}
Protecting API routes
Apply the token guard with RequireAuth on your API group. Pass an empty redirectTo to return 401 JSON instead of redirecting:
tokenGuard := &TokenGuard{DB: db}
api := app.Router.Group("/api")
api.Use(auth.RequireAuth(tokenGuard, ""))
api.Get("/me", MeHandler)
Token expiration
Tokens should have a TTL. The guard checks expires_at when validating. You can also run a periodic cleanup to delete expired tokens:
// Clean up expired tokens
db.Where("expires_at < ?", time.Now()).Delete(&AccessToken{})