Skip to main content

Authentication & Authorization

Developer Advanced

Secure your APIs with JWT authentication and RBAC authorization.

Authentication (Who are you?)

JWT Token Flow

1. User submits credentials → 2. Server validates → 3. Server returns JWT token

4. Client stores token → 5. Client sends token in requests → 6. Server validates token

Protecting Routes

// Require authentication
router.GET("/profile",
middleware.RequireAuth(),
controller.GetProfile,
)

// Public route (no auth)
router.GET("/public", controller.PublicEndpoint)

Getting Current User

func (c *UserController) GetProfile(ctx *gin.Context) {
// Get user ID from context (set by RequireAuth middleware)
userID, exists := ctx.Get("user_id")
if !exists {
util.RespondWithError(ctx, util.NewErrorMessage("E4012", "Unauthorized"))
return
}

// Get user from database
user, err := c.svc.User().GetByID(ctx.Request.Context(), userID.(string))
// ...
}

Authorization (What can you do?)

RBAC Structure

User → Roles → Permission Groups → Permissions

Permission Naming

Use format: resource:action

Examples:

  • users:read - View users
  • users:write - Create/update users
  • users:delete - Delete users
  • admin:all - All permissions

Protecting with Permissions

// Single permission
router.POST("/users",
middleware.RequireAuth(),
middleware.RequirePermission("users:create"),
controller.CreateUser,
)

// Multiple permissions (OR logic)
router.DELETE("/users/:id",
middleware.RequireAuth(),
middleware.RequireAnyPermission("users:delete", "admin:all"),
controller.DeleteUser,
)

// Multiple permissions (AND logic)
router.PUT("/users/:id/role",
middleware.RequireAuth(),
middleware.RequireAllPermissions("users:write", "roles:assign"),
controller.AssignRole,
)

Check Permission in Code

func (c *UserController) UpdateUser(ctx *gin.Context) {
userID := ctx.Param("id")
currentUserID := ctx.GetString("user_id")

// Check if user is updating their own profile or has admin permission
if userID != currentUserID {
hasPermission := c.svc.User().HasPermission(currentUserID, "users:write")
if !hasPermission {
util.RespondWithError(ctx, util.NewErrorMessage("E4031", "Permission denied"))
return
}
}

// Continue with update...
}

OAuth2/OIDC Integration

EZ-Console supports OAuth2 providers (Google, Okta, Azure AD, etc.):

Configuration

oauth:
enabled: true
providers:
- name: google
display_name: Google
client_id: your-client-id
client_secret: your-client-secret
auth_url: "https://accounts.google.com/o/oauth2/v2/auth"
token_url: "https://oauth2.googleapis.com/token"
user_info_url: "https://www.googleapis.com/oauth2/v2/userinfo"
redirect_url: "http://localhost:8080/api/auth/callback/google"

OAuth Flow

1. User clicks "Login with Google"

2. Redirect to Google login

3. User authorizes

4. Google redirects back with code

5. Exchange code for token

6. Fetch user info

7. Create/update user in database

8. Return JWT token

LDAP/Active Directory

Configuration

ldap:
enabled: true
host: "ldap.example.com"
port: 389
base_dn: "dc=example,dc=com"
user_dn: "ou=users,dc=example,dc=com"
bind_dn: "cn=admin,dc=example,dc=com"
bind_password: "admin-password"
user_filter: "(uid=%s)"

Multi-Factor Authentication (MFA)

TOTP (Time-based One-Time Password)

// Enable MFA for user
func (c *UserController) EnableMFA(ctx *gin.Context) {
userID := ctx.GetString("user_id")

// Generate secret
secret, qrCode, err := c.svc.User().GenerateMFASecret(ctx.Request.Context(), userID)
if err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E5001", "Failed to generate MFA secret", err))
return
}

util.RespondWithSuccess(ctx, http.StatusOK, map[string]interface{}{
"secret": secret,
"qr_code": qrCode,
})
}

// Verify MFA code
func (c *UserController) VerifyMFA(ctx *gin.Context) {
var req struct {
Code string `json:"code" binding:"required"`
}

if err := ctx.ShouldBindJSON(&req); err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "Invalid request", err))
return
}

userID := ctx.GetString("user_id")

valid, err := c.svc.User().VerifyMFACode(ctx.Request.Context(), userID, req.Code)
if err != nil || !valid {
util.RespondWithError(ctx, util.NewErrorMessage("E4001", "Invalid MFA code"))
return
}

util.RespondWithSuccess(ctx, http.StatusOK, map[string]interface{}{
"verified": true,
})
}

Service Accounts

For API-to-API authentication:

// Create service account
serviceAccount, apiKey, err := c.svc.ServiceAccount().Create(ctx.Request.Context(), CreateServiceAccountRequest{
Name: "External API",
Description: "For external integrations",
Permissions: []string{"products:read", "orders:read"},
})

// Use API key in requests
// Authorization: Bearer <api-key>

Best Practices

DO ✅

  1. Always use HTTPS in production
  2. Store passwords with strong hashing (bcrypt)
  3. Implement rate limiting on auth endpoints
  4. Use refresh tokens for long-lived sessions
  5. Implement account lockout after failed attempts
  6. Log all authentication events
  7. Enforce strong password policies

DON'T ❌

  1. Don't store passwords in plain text
  2. Don't put sensitive data in JWT claims
  3. Don't use predictable session IDs
  4. Don't ignore failed login attempts
  5. Don't skip permission checks
  6. Don't trust client-side authorization

Next Steps