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.
- 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
You can run ezauth as a separate service that handles authentication for your microservices.
-
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"
-
Build and Run: Build the binary from
cmd/ezauthapi/main.go.go build -o ezauthapi ./cmd/ezauthapi
Then, run the compiled binary:
./ezauthapi
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)
}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"]
// ...
}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)
})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)
})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.
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
}
}
}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 fromUserMetadatawith type casting.SetMeta(key, value): Sets a value inUserMetadata.GetAppMeta[T any](user, key) (T, bool): Retrieves a value fromAppMetadata.SetAppMeta(key, value): Sets a value inAppMetadata.
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 |
| 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.
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) |
ezauth provides several "plug and play" middlewares to protect your routes and manage user sessions. These are available directly on the EzAuth instance.
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.
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.
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.
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.
To generate the Swagger documentation, run:
make swaggerThe Swagger UI is available at /swagger/index.html.
Check out the _example directory for ready-to-use examples:
go-server: A complete, plug-and-play example showing how to integrateezauthwith a Go web server.javascript-client: An example JavaScript client interacting with theezauthAPI.