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{})