Skip to content

josuebrunel/ezauth

Repository files navigation

ezauth

Tests Documentation Go Reference

Simple and easy to use authentication library for Golang.

ezauth can be used as a standalone authentication service or embedded directly into your Go application as a library.

Features

  • Email/Password Authentication (Register, Login)
  • JWT based sessions (Access & Refresh Tokens, Refresh Token Rotation)
  • OAuth2 Support (Google, GitHub, Facebook)
  • Password Reset and Passwordless (Magic Link) authentication
  • Extended User Profiles (First Name, Last Name, Locale, Timezone, Roles, etc.)
  • SQLite, PostgreSQL, and MySQL support
  • API Key Protection for endpoints
  • Built-in Middleware for route protection
  • Swagger API Documentation

Usage

As a Standalone Service

You can run ezauth as a separate service that handles authentication for your microservices.

  1. Configuration: Set environment variables.

    export EZAUTH_ADDR=":8080"
    export EZAUTH_API_KEY="your-master-api-key"
    export EZAUTH_BASE_URL="http://localhost:8080"
    export EZAUTH_DB_DIALECT="sqlite3"  # or "postgres" or "mysql"
    export EZAUTH_DB_DSN="auth.db"      # for mysql: "user:pass@tcp(localhost:3306)/dbname?parseTime=true"
    export EZAUTH_DB_SCHEMA="public"    # Optional: Database schema (PostgreSQL only)
    export EZAUTH_JWT_SECRET="super-secret-key"
    
    # SMTP (Optional - for Email features)
    export EZAUTH_SMTP_HOST="smtp.example.com"
    export EZAUTH_SMTP_PORT="587"
    export EZAUTH_SMTP_USER="user@example.com"
    export EZAUTH_SMTP_PASSWORD="password"
    export EZAUTH_SMTP_FROM="noreply@example.com"
    
    # Email Templates (Optional - customize email content)
    # Uses Go text/template syntax: {{.Link}}, {{.Token}}, {{.Email}}
    export EZAUTH_EMAIL_PASSWORDLESS_SUBJECT="Magic Link Login"
    export EZAUTH_EMAIL_PASSWORDLESS_BODY="Click the following link to login: {{.Link}}"
    export EZAUTH_EMAIL_PASSWORD_RESET_SUBJECT="Password Reset Request"
    export EZAUTH_EMAIL_PASSWORD_RESET_BODY="Click the following link to reset your password: {{.Link}}"
    
    # Pages & Redirects (For Form-based auth)
    export EZAUTH_REDIRECT_AFTER_LOGIN="/"
    export EZAUTH_REDIRECT_AFTER_REGISTER="/"
    export EZAUTH_LOGIN_PAGE_URL="/login"
    export EZAUTH_REGISTER_PAGE_URL="/register"
    
    # OAuth2 (Optional)
    export EZAUTH_OAUTH2_CALLBACK_URL="http://localhost:3000/callback"
    
    # Google
    export EZAUTH_OAUTH2_GOOGLE_CLIENT_ID="your-google-client-id"
    export EZAUTH_OAUTH2_GOOGLE_CLIENT_SECRET="your-google-client-secret"
    export EZAUTH_OAUTH2_GOOGLE_REDIRECT_URL="http://localhost:8080/auth/oauth2/google/callback"
    export EZAUTH_OAUTH2_GOOGLE_SCOPES="email,profile"
    
    # GitHub
    export EZAUTH_OAUTH2_GITHUB_CLIENT_ID="your-github-client-id"
    export EZAUTH_OAUTH2_GITHUB_CLIENT_SECRET="your-github-client-secret"
    export EZAUTH_OAUTH2_GITHUB_REDIRECT_URL="http://localhost:8080/auth/oauth2/github/callback"
    export EZAUTH_OAUTH2_GITHUB_SCOPES="user:email"
    
    # Facebook
    export EZAUTH_OAUTH2_FACEBOOK_CLIENT_ID="your-facebook-client-id"
    export EZAUTH_OAUTH2_FACEBOOK_CLIENT_SECRET="your-facebook-client-secret"
    export EZAUTH_OAUTH2_FACEBOOK_REDIRECT_URL="http://localhost:8080/auth/oauth2/facebook/callback"
    export EZAUTH_OAUTH2_FACEBOOK_SCOPES="email"
  2. Build and Run: Build the binary from cmd/ezauthapi/main.go.

    go build -o ezauthapi ./cmd/ezauthapi

    Then, run the compiled binary:

    ./ezauthapi

As a Library

Embed ezauth directly into your existing Go application.

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/josuebrunel/ezauth"
    "github.com/josuebrunel/ezauth/pkg/config"
)

func main() {
    // 1. Setup Config
    os.Setenv("EZAUTH_API_KEY", "my-api-key")
    os.Setenv("EZAUTH_JWT_SECRET", "my-jwt-key")
    
    cfg, err := config.LoadConfig()
    if err != nil {
        log.Fatalf("Failed to load config: %v", err)
    }

    // 2. Initialize EzAuth
    auth, err := ezauth.New(&cfg, "")
    if err != nil {
        log.Fatalf("Failed to initialize auth: %v", err)
    }

    // 3. Run migrations
    if err := auth.Migrate(); err != nil {
        log.Fatalf("Failed to migrate: %v", err)
    }

    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    // 4. Add session middleware (handles sessions and user loading)
    r.Use(auth.SessionMiddleware)

    // 5. Mount Auth Routes
    r.Mount("/auth", auth.Handler)

    // Protected Route Example
    r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
        // Retrieve the authenticated user
        user, err := auth.GetSessionUser(r.Context())

        if err != nil {
            http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
            return
        }

        w.Write([]byte(fmt.Sprintf("Welcome, %s!", user.Email)))
    })

    http.ListenAndServe(":3000", r)
}

Session Management (Cookies)

When using the Form-based handlers, ezauth manages sessions using HTTP-only cookies via the scs session manager. The cookie name is ezauthsess.

Inside the session, the Access Token and Refresh Token are stored under the key tokens.

You can retrieve them in your application using the helper method:

tokens, err := auth.GetSessionTokens(ctx)
if err == nil {
    accessToken := tokens["access_token"]
    refreshToken := tokens["refresh_token"]
    // ...
}

Retrieving the Authenticated User

You can retrieve the full user object from the session using auth.GetSessionUser(ctx).

Important

You MUST mount the session middleware on your router for this to work.

// 1. Mount session middleware
r.Use(auth.SessionMiddleware)

// 2. In your handler
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
    if !auth.IsAuthenticated(r.Context()) {
        http.Redirect(w, r, "/login", http.StatusSeeOther)
        return
    }

    user, _ := auth.GetSessionUser(r.Context())
    fmt.Println("User:", user.Email)
})

Handling Errors and Success Messages

When using form-based handlers, errors and success messages are stored as flash messages in the session. Flash messages are one-time messages that are automatically cleared after being read.

r.Get("/login", func(w http.ResponseWriter, r *http.Request) {
    // Get flash messages (auto-cleared after read)
    errorMsg := auth.GetErrorMessage(r.Context())
    successMsg := auth.GetSuccessMessage(r.Context())

    // Pass to template for display
    data := map[string]string{
        "Error":   errorMsg,
        "Success": successMsg,
    }
    tmpl.Execute(w, data)
})

CSRF Protection

When using the form-based handlers (e.g., POST /auth/login), ezauth automatically enforces CSRF protection using filippo.io/csrf/gorilla and your EZAUTH_JWT_SECRET.

Note on Tokens vs Headers: This library relies entirely on modern browser Fetch Metadata headers (e.g. Sec-Fetch-Site, Origin) to enforce same-origin requests dynamically, mirroring the upcoming Go 1.25 standard library CSRF protections.

Because of this, hidden CSRF tokens in your HTML forms are completely optional and ignored during validation. However, if you are integrating with frontend frameworks or legacy systems that expect a token to be present, ezauth provides helpers to seamlessly generate dummy tokens to satisfy those requirements:

import "github.com/josuebrunel/ezauth"

// In your custom handler (ensure it's wrapped with the same CSRF middleware as ezauth)
r.Get("/my-custom-login", func(w http.ResponseWriter, r *http.Request) {
    data := map[string]interface{}{
        // Generate a pre-built <input type="hidden"> field
        "csrfField": ezauth.CSRFTemplateField(r),
        
        // Or get the raw string if you need it for AJAX headers (X-CSRF-Token)
        "csrfToken": ezauth.CSRFToken(r), 
    }
    tmpl.Execute(w, data)
})

Note

If you are using the JSON API endpoints (/auth/api/*) instead of the web forms, CSRF is disabled automatically since they use standard JWT Bearer Auth without cookies.

Helper Functions

ezauth provides package-level helper functions for convenient access to authentication context, useful in handlers or templates.

Important

These functions require the appropriate middleware (SessionMiddleware, LoadUserMiddleware, or AuthMiddleware) to be mounted on the router path.

import "github.com/josuebrunel/ezauth"

func MyHandler(w http.ResponseWriter, r *http.Request) {
    // Check if authenticated
    if ezauth.IsAuthenticated(r.Context()) {
        // ...
    }

    // Get User ID (works with both Session and JWT auth)
    userID, err := ezauth.GetUserID(r.Context())

    // Get User Object (requires LoadUserMiddleware or SessionMiddleware)
    user, err := ezauth.GetUser(r.Context())
    if err == nil {
        // Check for role
        if user.HasRole("admin") {
            // ...
        }

        // Get Metadata with type safety
        if theme, ok := models.GetMeta[string](user, "theme"); ok {
            // use theme
        }
    }
}

User Model Helpers

The User struct includes helper methods for common operations:

  • HasRole(role string) bool: Checks if the user has a specific role.
  • GetMeta[T any](user, key) (T, bool): Retrieves a value from UserMetadata with type casting.
  • SetMeta(key, value): Sets a value in UserMetadata.
  • GetAppMeta[T any](user, key) (T, bool): Retrieves a value from AppMetadata.
  • SetAppMeta(key, value): Sets a value in AppMetadata.

API Endpoints

Form-based Handlers (Cookies & Redirects)

These endpoints accept application/x-www-form-urlencoded, set secure cookies, and redirect.

Method Endpoint Description
POST /auth/register Register a new user
POST /auth/login Login and set cookies
POST /auth/logout Clear cookies and logout
POST /auth/password-reset/request Request password reset link
POST /auth/password-reset/confirm Confirm password reset
POST /auth/passwordless/request Request magic link
GET /auth/passwordless/login Login via magic link
GET /auth/oauth2/{provider}/login Login via OAuth2 provider
GET /auth/oauth2/{provider}/callback OAuth2 provider callback. URL: {base_url}/auth/oauth2/{provider}/callback

Form Field Reference

Endpoint Required Fields Optional Fields
/auth/register email, password, password_confirm first_name, last_name, locale, timezone, roles, meta_*
/auth/login email, password
/auth/password-reset/request email
/auth/password-reset/confirm token, password
/auth/passwordless/request email
/auth/passwordless/login token (query param)

Note

Passwords must be at least 8 characters long.

API Handlers (JSON)

These endpoints accept application/json and return JSON responses.

Method Endpoint Description
POST /auth/api/register Register a new user
POST /auth/api/login Login and receive tokens
POST /auth/api/token/refresh Refresh access token
POST /auth/api/password-reset/request Request password reset link
POST /auth/api/password-reset/confirm Confirm password reset
POST /auth/api/passwordless/request Request magic link
GET /auth/api/passwordless/login Login via magic link
GET /auth/api/userinfo Get current user info (Protected)
POST /auth/api/logout Revoke refresh token (Protected)
DELETE /auth/api/user Delete account (Protected)

Middlewares

ezauth provides several "plug and play" middlewares to protect your routes and manage user sessions. These are available directly on the EzAuth instance.

auth.SessionMiddleware

Usage: r.Use(auth.SessionMiddleware)

This is the recommended middleware for most applications. It combines session management and user loading.

  • Loads and saves session data (cookies).
  • Populates GetSessionUser(ctx) for downstream handlers.

auth.LoginRequiredMiddleware

Usage: r.Use(auth.LoginRequiredMiddleware)

Protects routes by requiring authentication.

  • Browser requests: Redirects to the configured EZAUTH_LOGIN_PAGE_URL.
  • API requests: Returns 401 Unauthorized.

auth.LoadUserMiddleware

Usage: r.Use(auth.LoadUserMiddleware)

Loads the user into the context without managing the session itself. Use this if you are using auth.Handler.Session.LoadAndSave manually or have a custom session setup.

auth.AuthMiddleware

Usage: r.Use(auth.AuthMiddleware)

Protects API routes using JWT Bearer tokens in the Authorization header.

  • Validates the token signature.
  • Sets the user ID in the context.

Swagger Documentation

To generate the Swagger documentation, run:

make swagger

The Swagger UI is available at /swagger/index.html.

Examples

Check out the _example directory for ready-to-use examples:

  • go-server: A complete, plug-and-play example showing how to integrate ezauth with a Go web server.
  • javascript-client: An example JavaScript client interacting with the ezauth API.