Web Application
Go (Fiber)

MojoAuth Hosted Login Page with Go (Fiber)

Introduction

Go (Golang) is a statically typed, compiled programming language designed for simplicity, efficiency, and reliability. Fiber is a Go web framework inspired by Express.js that combines the familiarity of a Node.js-like API with the performance of Go. Fiber is built on top of Fasthttp, the fastest HTTP engine for Go, and provides a range of features for building high-performance web applications.

This guide demonstrates how to integrate MojoAuth's Hosted Login Page with a Go application using the Fiber framework and the standard OpenID Connect (OIDC) flow for secure authentication.

Links:

Prerequisites

Before you begin, make sure you have:

  1. A MojoAuth account with an OIDC application configured
  2. Your OIDC Client ID, Client Secret, and Issuer URL (https://your-project.auth.mojoauth.com (opens in a new tab))
  3. Go 1.16+ installed
  4. Basic knowledge of Go and the Fiber framework

Project Setup

Let's start by creating a new Go project:

mkdir mojoauth-fiber-demo cd mojoauth-fiber-demo go mod init github.com/yourusername/mojoauth-fiber-demo

Install Required Dependencies

For OIDC authentication in Go with Fiber, we'll use the following packages:

go get github.com/gofiber/fiber/v2 go get github.com/gofiber/template/html go get github.com/gofiber/session/v2 go get github.com/coreos/go-oidc/v3/oidc go get golang.org/x/oauth2 go get github.com/joho/godotenv

Project Structure

Create the following project structure:

mojoauth-fiber-demo/ ├── config/ │ └── config.go ├── handlers/ │ └── auth.go ├── middleware/ │ └── auth.go ├── views/ │ ├── index.html │ ├── callback.html │ └── profile.html ├── main.go └── .env

Configure Environment Variables

Create a .env file in the root of your project:

MOJOAUTH_CLIENT_ID=your-client-id MOJOAUTH_CLIENT_SECRET=your-client-secret MOJOAUTH_ISSUER=https://your-project.auth.mojoauth.com MOJOAUTH_REDIRECT_URI=http://localhost:3000/callback MOJOAUTH_LOGOUT_REDIRECT_URI=http://localhost:3000/ SESSION_SECRET=your-secure-random-string PORT=3000

Set Up Configuration

Create the configuration file in config/config.go:

package config   import ( "log" "os"   "github.com/joho/godotenv" )   // Config holds all configuration for the application type Config struct { ClientID string ClientSecret string Issuer string RedirectURI string LogoutRedirectURI string SessionSecret string Port string }   // LoadConfig loads configuration from environment variables func LoadConfig() *Config { // Load .env file if it exists err := godotenv.Load() if err != nil { log.Println("No .env file found, using environment variables") }   // Set default port if not specified port := os.Getenv("PORT") if port == "" { port = "3000" }   return &Config{ ClientID: os.Getenv("MOJOAUTH_CLIENT_ID"), ClientSecret: os.Getenv("MOJOAUTH_CLIENT_SECRET"), Issuer: os.Getenv("MOJOAUTH_ISSUER"), RedirectURI: os.Getenv("MOJOAUTH_REDIRECT_URI"), LogoutRedirectURI: os.Getenv("MOJOAUTH_LOGOUT_REDIRECT_URI"), SessionSecret: os.Getenv("SESSION_SECRET"), Port: port, } }

Create Authentication Handlers

Create the authentication handlers in handlers/auth.go:

package handlers   import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "fmt"   "github.com/coreos/go-oidc/v3/oidc" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/session" "golang.org/x/oauth2"   "github.com/yourusername/mojoauth-fiber-demo/config" )   // AuthHandler handles all authentication-related requests type AuthHandler struct { oauth2Config *oauth2.Config verifier *oidc.IDTokenVerifier config *config.Config store *session.Store }   // NewAuthHandler creates a new AuthHandler func NewAuthHandler(cfg *config.Config, store *session.Store) (*AuthHandler, error) { ctx := context.Background() provider, err := oidc.NewProvider(ctx, cfg.Issuer) if err != nil { return nil, fmt.Errorf("failed to get provider: %v", err) }   oauth2Config := &oauth2.Config{ ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, RedirectURL: cfg.RedirectURI, Endpoint: provider.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, }   verifier := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID})   return &AuthHandler{ oauth2Config: oauth2Config, verifier: verifier, config: cfg, store: store, }, nil }   // generateRandomState generates a random string for OAuth2 state parameter func generateRandomState() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { return "", err } state := base64.StdEncoding.EncodeToString(b) return state, nil }   // Home renders the home page func (h *AuthHandler) Home(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to get session") }  var claims map[string]interface{} authenticated := false  if userClaims := sess.Get("user_claims"); userClaims != nil { claims = userClaims.(map[string]interface{}) authenticated = true }  return c.Render("index", fiber.Map{ "authenticated": authenticated, "claims": claims, }) }   // Login initiates the OIDC authentication flow func (h *AuthHandler) Login(c *fiber.Ctx) error { state, err := generateRandomState() if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to generate state") }  sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to get session") }  sess.Set("state", state) if err := sess.Save(); err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to save session") }   authURL := h.oauth2Config.AuthCodeURL(state) return c.Redirect(authURL) }   // Callback handles the OIDC callback func (h *AuthHandler) Callback(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to get session") }  // Verify state to prevent CSRF expectedState := sess.Get("state") if expectedState == nil { return c.Render("callback", fiber.Map{ "error": "State not found in session", }) }  state := c.Query("state") if state != expectedState { return c.Render("callback", fiber.Map{ "error": "State mismatch", }) }   // Exchange code for token code := c.Query("code") oauth2Token, err := h.oauth2Config.Exchange(context.Background(), code) if err != nil { return c.Render("callback", fiber.Map{ "error": "Failed to exchange code for token: " + err.Error(), }) }   // Extract ID token rawIDToken, ok := oauth2Token.Extra("id_token").(string) if !ok { return c.Render("callback", fiber.Map{ "error": "No ID token found in OAuth2 token", }) }   // Verify ID token idToken, err := h.verifier.Verify(context.Background(), rawIDToken) if err != nil { return c.Render("callback", fiber.Map{ "error": "Failed to verify ID token: " + err.Error(), }) }   // Extract user claims var claims map[string]interface{} if err := idToken.Claims(&claims); err != nil { return c.Render("callback", fiber.Map{ "error": "Failed to extract claims: " + err.Error(), }) }   // Store tokens and claims in session sess.Set("access_token", oauth2Token.AccessToken) sess.Set("id_token", rawIDToken) sess.Set("refresh_token", oauth2Token.RefreshToken) sess.Set("user_claims", claims)  if err := sess.Save(); err != nil { return c.Render("callback", fiber.Map{ "error": "Failed to save session: " + err.Error(), }) }   // Redirect to profile page return c.Redirect("/profile") }   // Profile displays the user's profile information func (h *AuthHandler) Profile(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to get session") }  userClaims := sess.Get("user_claims") if userClaims == nil { return c.Redirect("/") }  claims := userClaims.(map[string]interface{}) accessToken := sess.Get("access_token") idToken := sess.Get("id_token")  // Format the claims for display claimsJSON, _ := json.MarshalIndent(claims, "", " ")  return c.Render("profile", fiber.Map{ "claims": claims, "claimsJSON": string(claimsJSON), "accessToken": accessToken, "idToken": idToken, }) }   // Logout logs the user out func (h *AuthHandler) Logout(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to get session") }  // Clear session if err := sess.Destroy(); err != nil { return c.Status(fiber.StatusInternalServerError).SendString("Failed to destroy session") }  // Redirect to MojoAuth logout endpoint with client_id and post_logout_redirect_uri logoutURL := fmt.Sprintf( "%s/oauth2/sessions/logout?client_id=%s&post_logout_redirect_uri=%s", h.config.Issuer, h.config.ClientID, h.config.LogoutRedirectURI, )  return c.Redirect(logoutURL) }   // ProtectedAPI handles protected API requests func (h *AuthHandler) ProtectedAPI(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to get session", }) }  userClaims := sess.Get("user_claims") if userClaims == nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Unauthorized", }) }  claims := userClaims.(map[string]interface{})  return c.JSON(fiber.Map{ "message": "This is a protected API endpoint", "user": claims, }) }

Create Authentication Middleware

Create the authentication middleware in middleware/auth.go:

package middleware   import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/session" )   // Store is the session store used by the middleware var Store *session.Store   // RequireAuth ensures the user is authenticated func RequireAuth() fiber.Handler { return func(c *fiber.Ctx) error { sess, err := Store.Get(c) if err != nil { return c.Redirect("/") }  userClaims := sess.Get("user_claims") if userClaims == nil { return c.Redirect("/") }  return c.Next() } }   // APIAuth handles API authentication func APIAuth() fiber.Handler { return func(c *fiber.Ctx) error { sess, err := Store.Get(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Unauthorized", }) }  userClaims := sess.Get("user_claims") if userClaims == nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Unauthorized", }) }  return c.Next() } }

Create HTML Templates

views/index.html

<!DOCTYPE html> <html lang="en"> <head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Home - MojoAuth Fiber Demo</title>  <style>  body {  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;  line-height: 1.6;  color: #333;  max-width: 800px;  margin: 0 auto;  padding: 20px;  background-color: #f9fafb;  }  .container {  background: white;  border-radius: 8px;  padding: 30px;  box-shadow: 0 2px 4px rgba(0,0,0,0.1);  }  h1 {  color: #00ACD7;  margin-top: 0;  }  .btn {  display: inline-block;  background-color: #00ACD7;  color: white;  padding: 10px 20px;  border-radius: 4px;  text-decoration: none;  font-weight: 500;  transition: background-color 0.2s;  }  .btn:hover {  background-color: #0098c0;  }  .features {  display: flex;  gap: 20px;  margin: 30px 0;  }  .feature {  flex: 1;  background: #f3f4f6;  padding: 15px;  border-radius: 6px;  }  .feature h3 {  margin-top: 0;  }  .welcome {  background-color: #e0f7fa;  padding: 15px;  border-radius: 6px;  margin-bottom: 20px;  }  </style> </head> <body>  <div class="container">  <h1>MojoAuth Go/Fiber Demo</h1>    {{if .authenticated}}  <div class="welcome">  <h2>Welcome back!</h2>  <p>You are logged in as {{index .claims "name"}}.</p>  <a href="/profile" class="btn">View Profile</a>  <a href="/logout" style="margin-left: 10px;">Logout</a>  </div>  {{else}}  <p>  This application demonstrates how to integrate MojoAuth's Hosted Login Page   with a Go application using the Fiber framework and OpenID Connect.  </p>  <a href="/login" class="btn">Login with MojoAuth</a>    <div class="features">  <div class="feature">  <h3>🔒 Secure Authentication</h3>  <p>OpenID Connect provides a secure authentication layer on top of OAuth 2.0.</p>  </div>  <div class="feature">  <h3>🚀 High Performance</h3>  <p>Fiber is built on top of Fasthttp, the fastest HTTP engine for Go.</p>  </div>  <div class="feature">  <h3>🔑 Passwordless Options</h3>  <p>Support for email magic links, SMS OTP, and social login.</p>  </div>  </div>  {{end}}  </div> </body> </html>

views/callback.html

<!DOCTYPE html> <html lang="en"> <head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Processing Login - MojoAuth Fiber Demo</title>  <style>  body {  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;  line-height: 1.6;  color: #333;  max-width: 800px;  margin: 0 auto;  padding: 20px;  background-color: #f9fafb;  }  .container {  background: white;  border-radius: 8px;  padding: 30px;  box-shadow: 0 2px 4px rgba(0,0,0,0.1);  text-align: center;  }  h1 {  color: #00ACD7;  margin-top: 0;  }  .error {  background-color: #fee2e2;  color: #b91c1c;  padding: 15px;  border-radius: 6px;  margin: 20px 0;  }  .btn {  display: inline-block;  background-color: #00ACD7;  color: white;  padding: 10px 20px;  border-radius: 4px;  text-decoration: none;  font-weight: 500;  transition: background-color 0.2s;  }  .btn:hover {  background-color: #0098c0;  }  .spinner {  border: 4px solid rgba(0, 0, 0, 0.1);  border-radius: 50%;  border-top: 4px solid #00ACD7;  width: 40px;  height: 40px;  margin: 20px auto;  animation: spin 1s linear infinite;  }  @keyframes spin {  0% { transform: rotate(0deg); }  100% { transform: rotate(360deg); }  }  </style> </head> <body>  <div class="container">  {{if .error}}  <h1>Authentication Error</h1>  <div class="error">  <p>{{.error}}</p>  </div>  <a href="/" class="btn">Return to Home</a>  {{else}}  <h1>Processing Login</h1>  <div class="spinner"></div>  <p>Please wait while we complete your authentication...</p>  {{end}}  </div> </body> </html>

views/profile.html

<!DOCTYPE html> <html lang="en"> <head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>User Profile - MojoAuth Fiber Demo</title>  <style>  body {  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;  line-height: 1.6;  color: #333;  max-width: 800px;  margin: 0 auto;  padding: 20px;  background-color: #f9fafb;  }  .container {  background: white;  border-radius: 8px;  padding: 30px;  box-shadow: 0 2px 4px rgba(0,0,0,0.1);  }  h1 {  color: #00ACD7;  margin-top: 0;  }  .profile-header {  display: flex;  align-items: center;  margin-bottom: 30px;  padding-bottom: 20px;  border-bottom: 1px solid #e5e7eb;  }  .profile-avatar {  width: 100px;  height: 100px;  border-radius: 50%;  background-color: #00ACD7;  color: white;  display: flex;  align-items: center;  justify-content: center;  font-size: 40px;  font-weight: bold;  margin-right: 20px;  }  .profile-avatar img {  width: 100%;  height: 100%;  border-radius: 50%;  object-fit: cover;  }  .profile-info h2 {  margin-top: 0;  margin-bottom: 5px;  }  .profile-email {  color: #6b7280;  margin: 0;  }  .btn {  display: inline-block;  background-color: #00ACD7;  color: white;  padding: 10px 20px;  border-radius: 4px;  text-decoration: none;  font-weight: 500;  transition: background-color 0.2s;  }  .btn:hover {  background-color: #0098c0;  }  .btn-light {  background-color: #e5e7eb;  color: #4b5563;  }  .btn-light:hover {  background-color: #d1d5db;  }  .section {  margin-bottom: 30px;  }  .section h3 {  margin-top: 0;  color: #4b5563;  font-size: 18px;  }  table {  width: 100%;  border-collapse: collapse;  }  table td {  padding: 10px 0;  border-bottom: 1px solid #e5e7eb;  }  table td:first-child {  width: 30%;  color: #6b7280;  font-weight: 500;  }  .token-card {  background-color: #f3f4f6;  padding: 15px;  border-radius: 6px;  }  .token-display {  background-color: #e5e7eb;  padding: 10px;  border-radius: 4px;  font-family: monospace;  overflow-x: auto;  white-space: pre-wrap;  margin-bottom: 15px;  }  pre {  background-color: #f3f4f6;  padding: 15px;  border-radius: 6px;  overflow-x: auto;  white-space: pre-wrap;  font-size: 14px;  }  .actions {  display: flex;  gap: 10px;  margin-top: 30px;  }  </style> </head> <body>  <div class="container">  <h1>User Profile</h1>    <div class="profile-header">  {{if index .claims "picture"}}  <div class="profile-avatar">  <img src="{{index .claims "picture"}}" alt="Profile picture">  </div>  {{else}}  <div class="profile-avatar">  {{slice (index .claims "name") 0 1}}  </div>  {{end}}    <div class="profile-info">  <h2>{{index .claims "name"}}</h2>  <p class="profile-email">{{index .claims "email"}}</p>  </div>  </div>    <div class="section">  <h3>Profile Information</h3>  <table>  <tr>  <td>User ID:</td>  <td>{{index .claims "sub"}}</td>  </tr>  {{if index .claims "name"}}  <tr>  <td>Name:</td>  <td>{{index .claims "name"}}</td>  </tr>  {{end}}  {{if index .claims "email"}}  <tr>  <td>Email:</td>  <td>{{index .claims "email"}}</td>  </tr>  {{end}}  {{if index .claims "email_verified"}}  <tr>  <td>Email Verified:</td>  <td>{{if index .claims "email_verified"}}Yes{{else}}No{{end}}</td>  </tr>  {{end}}  </table>  </div>    <div class="section">  <h3>Token Information</h3>  <div class="token-card">  <h4>ID Token</h4>  <div class="token-display">{{slice .idToken 0 40}}...</div>    <h4>Access Token</h4>  <div class="token-display">{{slice .accessToken 0 40}}...</div>  </div>  </div>    <div class="section">  <h3>Raw Profile Data</h3>  <pre>{{.claimsJSON}}</pre>  </div>    <div class="actions">  <a href="/" class="btn">Home</a>  <a href="/logout" class="btn btn-light">Logout</a>  </div>  </div> </body> </html>

Create Main Application

Now, create the main application file main.go:

package main   import ( "fmt" "log"   "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/session" "github.com/gofiber/template/html"   "github.com/yourusername/mojoauth-fiber-demo/config" "github.com/yourusername/mojoauth-fiber-demo/handlers" "github.com/yourusername/mojoauth-fiber-demo/middleware" )   func main() { // Load configuration cfg := config.LoadConfig()   // Initialize template engine engine := html.New("./views", ".html")   // Create new Fiber app app := fiber.New(fiber.Config{ Views: engine, })   // Add middleware app.Use(logger.New())   // Initialize session store store := session.New(session.Config{ CookieHTTPOnly: true, CookieSecure: false, // Set to true in production with HTTPS Expiration: 86400 * 1000, // 24 hours KeyLookup: "cookie:mojoauth_session", CookiePath: "/", })   // Make session store available to middleware middleware.Store = store   // Create auth handler authHandler, err := handlers.NewAuthHandler(cfg, store) if err != nil { log.Fatalf("Failed to create auth handler: %v", err) }   // Static files app.Static("/static", "./static")   // Routes app.Get("/", authHandler.Home) app.Get("/login", authHandler.Login) app.Get("/callback", authHandler.Callback) app.Get("/logout", authHandler.Logout)   // Protected routes app.Get("/profile", middleware.RequireAuth(), authHandler.Profile)   // API routes api := app.Group("/api", middleware.APIAuth()) api.Get("/protected", authHandler.ProtectedAPI)   // Start server addr := fmt.Sprintf(":%s", cfg.Port) log.Printf("Starting server on http://localhost%s", addr) if err := app.Listen(addr); err != nil { log.Fatalf("Server failed to start: %v", err) } }

Create the Static Directory

mkdir -p static/css

You can add any static files like CSS, JavaScript, and images here if needed.

Testing the Authentication Flow

  1. Start your Go application:
go run main.go
  1. Open your browser and navigate to http://localhost:3000

  2. Click the "Login with MojoAuth" button

  3. You'll be redirected to the MojoAuth Hosted Login Page

  4. After successful authentication, you'll be redirected back to your application

  5. You should now see your user profile information

Making Authenticated API Requests

You can now make authenticated API requests to your /api/protected endpoint:

# First login through the web interface, then use the access token curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" http://localhost:3000/api/protected

Production Considerations

1. Environment Configuration

For production deployment, use environment variables or a secure secret management system instead of a .env file:

export MOJOAUTH_CLIENT_ID=your-client-id export MOJOAUTH_CLIENT_SECRET=your-client-secret export MOJOAUTH_ISSUER=https://your-project.auth.mojoauth.com export MOJOAUTH_REDIRECT_URI=https://your-domain.com/callback export MOJOAUTH_LOGOUT_REDIRECT_URI=https://your-domain.com/ export SESSION_SECRET=your-secure-random-string export PORT=3000

2. HTTPS in Production

Always use HTTPS in production. You can configure your Go application to use TLS directly:

// In main.go import ( "crypto/tls" )   // Configure TLS certFile := "./cert/cert.pem" keyFile := "./cert/key.pem"   // Create TLS configuration tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, }   // Start server with TLS log.Printf("Starting server with TLS on https://localhost%s", addr) if err := app.ListenTLS(addr, certFile, keyFile, tlsConfig); err != nil { log.Fatalf("Server failed to start: %v", err) }

Alternatively, use a reverse proxy like Nginx or Caddy to handle TLS termination.

3. Session Security

For production, configure more secure session settings:

store := session.New(session.Config{ KeyLookup: "cookie:mojoauth_session", CookieSecure: true, // Require HTTPS CookieHTTPOnly: true, CookieSameSite: "Lax", CookieDomain: "your-domain.com", // Set to your domain CookiePath: "/", Expiration: 86400 * 1000, // 24 hours })

For large applications, consider using Redis for session storage:

go get github.com/gofiber/storage/redis
import ( "github.com/gofiber/storage/redis" )   // Initialize Redis storage redisStorage := redis.New(redis.Config{ Host: "127.0.0.1", Port: 6379, Username: "", Password: "", Database: 0, })   // Use Redis storage for sessions store := session.New(session.Config{ Storage: redisStorage, // Other session options })

4. Structured Logging

Implement structured logging for better production monitoring:

go get github.com/sirupsen/logrus
import ( "github.com/sirupsen/logrus" )   // Initialize logger logger := logrus.New() logger.SetFormatter(&logrus.JSONFormatter{})   if os.Getenv("ENVIRONMENT") == "production" { logger.SetLevel(logrus.InfoLevel) } else { logger.SetLevel(logrus.DebugLevel) }   // Use logger in your application logger.WithFields(logrus.Fields{ "user_id": userID, "action": "login", }).Info("User logged in")

5. Token Validation

In production, implement more robust token validation:

// In middleware/auth.go import ( "context" "strings"   "github.com/coreos/go-oidc/v3/oidc" "github.com/gofiber/fiber/v2" )   // BearerAuth middleware for API endpoints using Bearer token func BearerAuth(verifier *oidc.IDTokenVerifier) fiber.Handler { return func(c *fiber.Ctx) error { // Get token from Authorization header authHeader := c.Get("Authorization") if authHeader == "" { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "No Authorization header provided", }) }  // Extract the token parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Invalid Authorization header format", }) }  token := parts[1]  // Verify the token _, err := verifier.Verify(context.Background(), token) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Invalid token", }) }  // Continue to the next handler return c.Next() } }

Advanced Features

1. Token Refresh

Implement token refresh to maintain user sessions:

// In handlers/auth.go // RefreshToken refreshes the access token func (h *AuthHandler) RefreshToken(c *fiber.Ctx) error { sess, err := h.store.Get(c) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to get session", }) }  refreshToken := sess.Get("refresh_token") if refreshToken == nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "No refresh token available", }) }  token := &oauth2.Token{ RefreshToken: refreshToken.(string), }  newToken, err := h.oauth2Config.TokenSource(context.Background(), token).Token() if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": fmt.Sprintf("Failed to refresh token: %v", err), }) }  // Extract ID token rawIDToken, ok := newToken.Extra("id_token").(string) if !ok { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "No ID token found in refreshed token", }) }  // Update session with new tokens sess.Set("access_token", newToken.AccessToken) sess.Set("id_token", rawIDToken) sess.Set("refresh_token", newToken.RefreshToken)  if err := sess.Save(); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": fmt.Sprintf("Failed to save session: %v", err), }) }  // Return success response return c.JSON(fiber.Map{ "status": "success", "message": "Token refreshed successfully", }) }

Add the route to your main application:

app.Post("/refresh-token", middleware.RequireAuth(), authHandler.RefreshToken)

2. Role-Based Access Control

Implement role-based access control using claims from the ID token:

// In middleware/auth.go // HasRole checks if a user has a specific role func HasRole(claims map[string]interface{}, role string) bool { // Check if the claims contain roles rolesInterface, ok := claims["roles"] if !ok { return false }  // Check if roles is a slice roles, ok := rolesInterface.([]interface{}) if !ok { return false }  // Check if the role exists in the slice for _, r := range roles { if roleStr, ok := r.(string); ok && roleStr == role { return true } }  return false }   // RequireRole middleware ensures the user has a specific role func RequireRole(role string) fiber.Handler { return func(c *fiber.Ctx) error { sess, err := Store.Get(c) if err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Unauthorized", }) }  userClaims := sess.Get("user_claims") if userClaims == nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "Unauthorized", }) }  claims := userClaims.(map[string]interface{}) if !HasRole(claims, role) { return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ "error": "Forbidden: Insufficient permissions", }) }  return c.Next() } }

Use the middleware for role-protected routes:

// Admin routes admin := app.Group("/admin", middleware.RequireAuth(), middleware.RequireRole("admin")) admin.Get("/dashboard", adminHandler.Dashboard)

3. User Management Service

Create a separate service for user management operations:

// userservice/userservice.go package userservice   import ( "context" "encoding/json" "errors" "net/http" )   // UserService provides user management functionality type UserService struct { issuerURL string }   // NewUserService creates a new UserService func NewUserService(issuerURL string) *UserService { return &UserService{ issuerURL: issuerURL, } }   // GetUserInfo retrieves user information from the UserInfo endpoint func (s *UserService) GetUserInfo(accessToken string) (map[string]interface{}, error) { if accessToken == "" { return nil, errors.New("no access token provided") }  // Create request to UserInfo endpoint req, err := http.NewRequest("GET", s.issuerURL+"/oauth/userinfo", nil) if err != nil { return nil, err }  req.Header.Set("Authorization", "Bearer "+accessToken)  client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close()  if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to get user info") }  var userInfo map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { return nil, err }  return userInfo, nil }

Next Steps

  • Implement role-based access control using claims from the ID token
  • Add support for multiple authentication methods (passwordless, social login)
  • Enhance error handling and user feedback mechanisms
  • Implement account linking for users with multiple login methods
  • Add comprehensive logging and monitoring for authentication events

Reference Links