Skip to Content
DocumentationGuidesAuthentication

Authentication

The scaffold imports pkg/auth as its default authentication layer — JWT tokens for stateless auth, Casbin for role-based access control, and a starter User model with login and registration endpoints in every new project. Like every pkg/* default, it is opt-out: a developer who prefers a different JWT library or RBAC engine can delete the pkg/auth import and go get an alternative without touching the rest of the project.

Overview

The authentication system consists of:

  • JWT tokens for stateless authentication (github.com/gofastadev/gofasta/pkg/auth)
  • Auth middleware for protecting routes (github.com/gofastadev/gofasta/pkg/middleware)
  • Casbin RBAC for role-based access control
  • Pre-built auth controller with register, login, and token refresh endpoints

JWT Configuration

JWT settings live in config.yaml:

jwt: secret: your-secret-key-change-in-production expiration: 24h refresh_expiration: 168h # 7 days issuer: myapp

Override in production with environment variables:

GOFASTA_JWT_SECRET=your-production-secret GOFASTA_JWT_EXPIRATION=1h

Registration and Login Flow

Register

The built-in auth controller handles user registration:

curl -X POST http://localhost:8080/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123", "first_name": "Jane", "last_name": "Doe" }'

Response:

{ "success": true, "data": { "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "first_name": "Jane", "last_name": "Doe", "role": "user" }, "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs..." } }

Login

curl -X POST http://localhost:8080/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123" }'

The response includes both an access token and a refresh token.

Token Refresh

When the access token expires, use the refresh token to get a new pair:

curl -X POST http://localhost:8080/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "eyJhbGciOiJIUzI1NiIs..." }'

Auth Service Implementation

The auth service handles password hashing, token generation, and user lookup:

// app/services/auth.service.go package services import ( "context" "errors" "github.com/gofastadev/gofasta/pkg/auth" "myapp/app/dtos" "myapp/app/models" "myapp/app/repositories/interfaces" ) type AuthService struct { userRepo interfaces.UserRepository jwtConfig *auth.JWTConfig } func NewAuthService(userRepo interfaces.UserRepository, jwtConfig *auth.JWTConfig) *AuthService { return &AuthService{userRepo: userRepo, jwtConfig: jwtConfig} } func (s *AuthService) Register(ctx context.Context, req *dtos.RegisterRequest) (*dtos.AuthResponse, error) { hashedPassword, err := auth.HashPassword(req.Password) if err != nil { return nil, err } user := &models.User{ Email: req.Email, Password: hashedPassword, FirstName: req.FirstName, LastName: req.LastName, Role: "user", } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } accessToken, err := auth.GenerateToken(user.ID.String(), user.Role, s.jwtConfig) if err != nil { return nil, err } refreshToken, err := auth.GenerateRefreshToken(user.ID.String(), s.jwtConfig) if err != nil { return nil, err } return &dtos.AuthResponse{ User: user.ToResponse(), AccessToken: accessToken, RefreshToken: refreshToken, }, nil } func (s *AuthService) Login(ctx context.Context, req *dtos.LoginRequest) (*dtos.AuthResponse, error) { user, err := s.userRepo.FindByEmail(ctx, req.Email) if err != nil { return nil, errors.New("invalid credentials") } if !auth.CheckPassword(req.Password, user.Password) { return nil, errors.New("invalid credentials") } accessToken, err := auth.GenerateToken(user.ID.String(), user.Role, s.jwtConfig) if err != nil { return nil, err } refreshToken, err := auth.GenerateRefreshToken(user.ID.String(), s.jwtConfig) if err != nil { return nil, err } return &dtos.AuthResponse{ User: user.ToResponse(), AccessToken: accessToken, RefreshToken: refreshToken, }, nil }

Auth Package Functions

The github.com/gofastadev/gofasta/pkg/auth package provides these functions:

FunctionDescription
auth.HashPassword(password)Bcrypt hash a plaintext password
auth.CheckPassword(password, hash)Compare plaintext against bcrypt hash
auth.GenerateToken(userID, role, config)Create a signed JWT access token
auth.GenerateRefreshToken(userID, config)Create a signed JWT refresh token
auth.ValidateToken(tokenString, config)Parse and validate a JWT token
auth.ExtractClaims(tokenString, config)Extract claims from a valid token

Protecting Routes with Middleware

The auth middleware from github.com/gofastadev/gofasta/pkg/middleware validates JWT tokens and sets user information on the request context.

import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/middleware" ) // Apply to a route group — chi's Group attaches shared middleware to every // route registered inside the callback without mounting at a new prefix. api.Group(func(protected chi.Router) { protected.Use(middleware.Auth(jwtConfig)) // All routes in this group require a valid JWT protected.Get("/profile", httputil.Handle(userController.GetProfile)) protected.Put("/profile", httputil.Handle(userController.UpdateProfile)) })

The middleware uses the standard func(http.Handler) http.Handler signature and:

  1. Extracts the Authorization: Bearer <token> header
  2. Validates the token signature and expiration
  3. Sets claims in the request context (accessed via r.Context())
  4. Returns 401 if the token is missing or invalid

Access user info in controllers:

func (c *UserController) GetProfile(w http.ResponseWriter, r *http.Request) error { claims := auth.ClaimsFromContext(r.Context()) userID := claims.UserID role := claims.Role user, err := c.service.FindByID(r.Context(), userID) if err != nil { return err } return httputil.OK(w, user) }

Role-Based Access Control (RBAC) with Casbin

Gofasta uses Casbin  for fine-grained role-based access control. Casbin policies define who can access which resources.

Policy Configuration

RBAC policies are defined in configs/rbac_policy.csv:

p, admin, /api/v1/*, * p, admin, /api/v1/admin/*, * p, user, /api/v1/products, GET p, user, /api/v1/products/{id}, GET p, user, /api/v1/profile, (GET)|(PUT) p, moderator, /api/v1/products, * p, moderator, /api/v1/products/{id}, *

Each line defines a policy rule: p, role, resource, action.

The RBAC model is defined in configs/rbac_model.conf:

[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)

Applying RBAC Middleware

Stack the RBAC middleware after the auth middleware:

import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/middleware" ) // Load the Casbin enforcer enforcer := middleware.NewCasbinEnforcer("configs/rbac_model.conf", "configs/rbac_policy.csv") // Admin routes require both authentication and admin role api.Route("/api/v1/admin", func(admin chi.Router) { admin.Use(middleware.Auth(jwtConfig)) admin.Use(middleware.RBAC(enforcer)) admin.Get("/users", httputil.Handle(adminController.ListUsers)) admin.Delete("/users/{id}", httputil.Handle(adminController.DeleteUser)) })

The RBAC middleware uses the standard func(http.Handler) http.Handler signature and:

  1. Reads the role from the request context (set by the auth middleware)
  2. Checks the Casbin policy to see if the role can access the requested path with the given HTTP method
  3. Returns 403 Forbidden if the policy denies access

Role Hierarchy

Define role inheritance in the policy file using g rules:

g, admin, moderator g, moderator, user

This means admin inherits all permissions from moderator, which inherits from user.

Dynamic Policies

You can modify policies at runtime through the Casbin enforcer:

// Add a policy enforcer.AddPolicy("editor", "/api/v1/articles", "PUT") // Remove a policy enforcer.RemovePolicy("editor", "/api/v1/articles", "PUT") // Check a permission allowed, _ := enforcer.Enforce("user", "/api/v1/products", "GET")

Auth DTOs

// app/dtos/auth.dtos.go package dtos type RegisterRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` FirstName string `json:"first_name" validate:"required"` LastName string `json:"last_name" validate:"required"` } type LoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } type RefreshRequest struct { RefreshToken string `json:"refresh_token" validate:"required"` } type AuthResponse struct { User *UserResponse `json:"user"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` }

Next Steps

Last updated on