{"id":35843191,"url":"https://github.com/appleboy/authgate","last_synced_at":"2026-01-24T09:03:20.931Z","repository":{"id":331560267,"uuid":"1128824463","full_name":"appleboy/authgate","owner":"appleboy","description":"AuthGate is an authorization server built on the OAuth 2.0 Device Authorization Grant (RFC 8628) standard, developed using Go and the Gin framework.","archived":false,"fork":false,"pushed_at":"2026-01-18T04:35:42.000Z","size":470,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T08:15:09.671Z","etag":null,"topics":["authorization","authorization-code-grant","device","oauth2"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appleboy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":["https://www.paypal.me/appleboy46"]}},"created_at":"2026-01-06T07:49:29.000Z","updated_at":"2026-01-18T04:34:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/appleboy/authgate","commit_stats":null,"previous_names":["appleboy/authgate"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/appleboy/authgate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appleboy%2Fauthgate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appleboy%2Fauthgate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appleboy%2Fauthgate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appleboy%2Fauthgate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appleboy","download_url":"https://codeload.github.com/appleboy/authgate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appleboy%2Fauthgate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28721989,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T08:27:05.734Z","status":"ssl_error","status_checked_at":"2026-01-24T08:27:01.197Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","authorization-code-grant","device","oauth2"],"created_at":"2026-01-08T04:18:04.253Z","updated_at":"2026-01-24T09:03:20.923Z","avatar_url":"https://github.com/appleboy.png","language":"Go","funding_links":["https://www.paypal.me/appleboy46"],"categories":[],"sub_categories":[],"readme":"# AuthGate\n\n\u003e A lightweight OAuth 2.0 Device Authorization Grant server for CLI tools and browserless devices\n\n[![Security Scanning](https://github.com/appleboy/authgate/actions/workflows/security.yml/badge.svg)](https://github.com/appleboy/authgate/actions/workflows/security.yml)\n[![Lint and Testing](https://github.com/appleboy/authgate/actions/workflows/testing.yml/badge.svg)](https://github.com/appleboy/authgate/actions/workflows/testing.yml)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n## Table of Contents\n\n- [AuthGate](#authgate)\n  - [Table of Contents](#table-of-contents)\n  - [Why AuthGate?](#why-authgate)\n    - [The Problem](#the-problem)\n    - [Real-World Scenarios](#real-world-scenarios)\n    - [The Solution](#the-solution)\n  - [Features](#features)\n  - [Quick Start](#quick-start)\n    - [Prerequisites](#prerequisites)\n    - [Installation](#installation)\n    - [Run the Server](#run-the-server)\n    - [Docker Deployment](#docker-deployment)\n      - [Docker Features](#docker-features)\n      - [Docker Compose Example](#docker-compose-example)\n    - [Test with the Example CLI](#test-with-the-example-cli)\n  - [User Interface](#user-interface)\n    - [1. Login Page](#1-login-page)\n    - [2. Device Authorization Page](#2-device-authorization-page)\n    - [3. Authorization Success](#3-authorization-success)\n    - [4. Session Management](#4-session-management)\n  - [How It Works](#how-it-works)\n    - [Device Flow Sequence](#device-flow-sequence)\n    - [Key Endpoints](#key-endpoints)\n      - [Endpoint Details](#endpoint-details)\n        - [Device Flow (CLI)](#device-flow-cli)\n        - [User Authorization (Browser)](#user-authorization-browser)\n        - [Token Validation](#token-validation)\n        - [Token Revocation (RFC 7009)](#token-revocation-rfc-7009)\n        - [Session Management (Web UI)](#session-management-web-ui)\n  - [Configuration](#configuration)\n    - [Environment Variables](#environment-variables)\n      - [Generate Strong Secrets](#generate-strong-secrets)\n    - [Default Test Data](#default-test-data)\n      - [User Account](#user-account)\n      - [OAuth Client](#oauth-client)\n    - [Pluggable Token Providers](#pluggable-token-providers)\n      - [Architecture](#architecture)\n      - [Token Provider Modes](#token-provider-modes)\n      - [HTTP API Contract](#http-api-contract)\n      - [Why Local Storage is Retained](#why-local-storage-is-retained)\n      - [Use Cases](#use-cases)\n      - [Migration Path](#migration-path)\n    - [Service-to-Service Authentication](#service-to-service-authentication)\n      - [Why Service-to-Service Authentication?](#why-service-to-service-authentication)\n      - [Authentication Modes](#authentication-modes)\n      - [Configuration per Service](#configuration-per-service)\n      - [Server-Side Verification Example](#server-side-verification-example)\n      - [Example: Securing External Authentication API](#example-securing-external-authentication-api)\n    - [HTTP Retry with Exponential Backoff](#http-retry-with-exponential-backoff)\n      - [Features](#features-1)\n      - [Default Behavior](#default-behavior)\n      - [Automatic Retry Conditions](#automatic-retry-conditions)\n      - [Configuration](#configuration-1)\n      - [Disable Retries](#disable-retries)\n      - [Use Cases](#use-cases-1)\n      - [Best Practices](#best-practices)\n      - [Example: Aggressive Retry Configuration](#example-aggressive-retry-configuration)\n      - [Example: Conservative Retry Configuration](#example-conservative-retry-configuration)\n      - [Implementation Details](#implementation-details)\n  - [AuthGate Architecture](#authgate-architecture)\n    - [Project Structure](#project-structure)\n    - [Technology Stack](#technology-stack)\n  - [Development](#development)\n    - [Build Commands](#build-commands)\n      - [Build Details](#build-details)\n    - [Database Schema](#database-schema)\n    - [Extending the Server](#extending-the-server)\n      - [Add a new OAuth client](#add-a-new-oauth-client)\n      - [Add custom scopes](#add-custom-scopes)\n  - [Monitoring and Observability](#monitoring-and-observability)\n    - [Health Check Endpoint](#health-check-endpoint)\n      - [Health Check Details](#health-check-details)\n    - [Monitoring Best Practices](#monitoring-best-practices)\n      - [Key Metrics to Monitor](#key-metrics-to-monitor)\n      - [Logging](#logging)\n  - [Security Considerations](#security-considerations)\n    - [Production Deployment Checklist](#production-deployment-checklist)\n    - [Threat Model](#threat-model)\n      - [What AuthGate Protects Against](#what-authgate-protects-against)\n      - [What You Must Secure](#what-you-must-secure)\n  - [Deployment](#deployment)\n    - [Production Deployment Options](#production-deployment-options)\n      - [1. Binary Deployment (Systemd)](#1-binary-deployment-systemd)\n      - [2. Docker Deployment](#2-docker-deployment)\n      - [3. Reverse Proxy Setup (Nginx)](#3-reverse-proxy-setup-nginx)\n      - [4. Cloud Platform Deployment](#4-cloud-platform-deployment)\n        - [Fly.io Example](#flyio-example)\n  - [Use Cases Sample](#use-cases-sample)\n    - [Example: Securing a CLI Tool](#example-securing-a-cli-tool)\n    - [Example: IoT Device Authentication](#example-iot-device-authentication)\n    - [Example: Security Incident Response](#example-security-incident-response)\n  - [Performance Considerations](#performance-considerations)\n    - [Scalability](#scalability)\n      - [Current Architecture (SQLite)](#current-architecture-sqlite)\n      - [For High-Scale Deployments](#for-high-scale-deployments)\n      - [Performance Tips](#performance-tips)\n    - [Benchmarks (Reference)](#benchmarks-reference)\n  - [Comparison with Other Solutions](#comparison-with-other-solutions)\n  - [Troubleshooting](#troubleshooting)\n    - [Common Issues](#common-issues)\n      - [Issue: \"Client not found\" error](#issue-client-not-found-error)\n      - [Issue: Database locked errors](#issue-database-locked-errors)\n      - [Issue: \"authorization\\_pending\" never resolves](#issue-authorization_pending-never-resolves)\n      - [Issue: \"Username conflict with existing user\" error](#issue-username-conflict-with-existing-user-error)\n      - [Issue: JWT signature verification fails](#issue-jwt-signature-verification-fails)\n      - [Issue: Session not persisting](#issue-session-not-persisting)\n    - [Debug Mode](#debug-mode)\n  - [FAQ](#faq)\n    - [Q: Why not use OAuth password grant?](#q-why-not-use-oauth-password-grant)\n    - [Q: Can I use this in production?](#q-can-i-use-this-in-production)\n    - [Q: How do I add user registration?](#q-how-do-i-add-user-registration)\n    - [Q: Can I use this with multiple clients?](#q-can-i-use-this-with-multiple-clients)\n    - [Q: What about token refresh?](#q-what-about-token-refresh)\n    - [Q: How do users revoke device access?](#q-how-do-users-revoke-device-access)\n    - [Q: How long do device codes last?](#q-how-long-do-device-codes-last)\n    - [Q: Can I use a different database?](#q-can-i-use-a-different-database)\n    - [Q: How do I change the polling interval?](#q-how-do-i-change-the-polling-interval)\n    - [Q: Are user codes case-sensitive?](#q-are-user-codes-case-sensitive)\n  - [Contributing](#contributing)\n  - [License](#license)\n  - [References](#references)\n  - [Acknowledgments](#acknowledgments)\n\n---\n\n## Why AuthGate?\n\n### The Problem\n\nModern CLI tools and IoT devices need to access user resources securely, but traditional OAuth 2.0 flows weren't designed for them:\n\n- **Authorization Code Flow** requires a browser redirect and a local callback server\n- **Client Credentials Flow** can't authenticate specific users\n- **Password Grant** requires users to enter credentials directly into apps (security risk)\n- Embedding `client_secret` in distributed applications is insecure\n\n### Real-World Scenarios\n\n- 🖥️ **CLI tools** (like `gh`, `aws-cli`) need to access user data\n- 📺 **Smart TVs** and streaming devices authenticating streaming services\n- 🏠 **IoT devices** that lack browsers or input capabilities\n- 🤖 **CI/CD pipelines** and automation scripts requiring user authorization\n- 🎮 **Gaming consoles** logging into online services\n\n### The Solution\n\n**Device Authorization Grant (RFC 8628)** solves this by splitting the authorization flow:\n\n1. Device requests a code from the server\n2. User visits a URL **on another device** (phone/computer) with a browser\n3. User logs in and enters the short code\n4. Device polls the server until authorization is complete\n5. Device receives an access token\n\n**AuthGate** provides a production-ready implementation of this flow that you can deploy in minutes.\n\n---\n\n## Features\n\n🔐 Comprehensive \u0026 Secure OAuth 2.0 Support\n\n- Fully implements OAuth 2.0 Device Authorization Grant (RFC 8628)\n- Complete Refresh Token support (RFC 6749)\n- Secure Token Revocation endpoint (RFC 7009)\n- Built‑in JWT Access Tokens signed with HMAC‑SHA256\n- Status‑based token lifecycle control (active / disabled / revoked)\n\n🧩 Flexible Authentication \u0026 Token Flows\n\n- Session-based authentication with encrypted cookies (7‑day expiry)\n- Pluggable token providers: use embedded JWT or external token services\n- Hybrid authentication: supports both local and external identity providers\n- Configurable refresh token modes:\n  - Fixed mode – reusable, multi-device friendly\n  - Rotation mode – high‑security token hygiene\n\n🛠️ Easy Deployment \u0026 Operational Simplicity\n\n- Lightweight single static binary with SQLite built-in\n- Zero external dependencies\n- Fully configurable via .env\n- Web UI for viewing and revoking active sessions and tokens\n- Token validation endpoint: /oauth/tokeninfo\n- Health check endpoint: /health\n- Graceful shutdown with safe signal handling\n- Automatic HTTP retry with exponential backoff\n\n📦 Cloud \u0026 Platform Friendly\n\n- Cross‑platform support for Linux, macOS, and Windows\n- Docker-ready with multi‑arch images and security hardening\n- Embedded templates and static files—no additional assets required\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Go 1.24 or higher\n- Make (optional, but recommended for convenience commands)\n\n### Installation\n\n```bash\n# Clone the repository\ngit clone \u003crepository-url\u003e\ncd authgate\n\n# Copy environment configuration\ncp .env.example .env\n\n# Edit .env and set your secrets\nnano .env\n\n# Build the server (outputs to bin/authgate with version info)\nmake build\n\n# Or build directly with Go\ngo build -o bin/authgate .\n```\n\n### Run the Server\n\n```bash\n# Show version information\n./bin/authgate -v\n./bin/authgate --version\n\n# Show help\n./bin/authgate -h\n\n# Start the server\n./bin/authgate server\n\n# Or directly with Go\ngo run . server\n```\n\nThe server will start on `http://localhost:8080` by default.\n\n**Important:** Note the `client_id` printed in the startup logs - you'll need this for the CLI example.\n\n### Docker Deployment\n\nAuthGate provides multi-architecture Docker images for easy deployment:\n\n```bash\n# Build for your platform\nmake build_linux_amd64  # For Linux x86_64\nmake build_linux_arm64  # For Linux ARM64\n\n# Build Docker image (with version tag)\ndocker build -f docker/Dockerfile \\\n  --build-arg VERSION=v1.0.0 \\\n  -t authgate:v1.0.0 \\\n  -t authgate:latest \\\n  .\n\n# Or build without version (defaults to \"dev\")\ndocker build -f docker/Dockerfile -t authgate .\n\n# Run with Docker\ndocker run -d \\\n  --name authgate \\\n  -p 8080:8080 \\\n  -v authgate-data:/app/data \\\n  -e JWT_SECRET=your-secret-here \\\n  -e SESSION_SECRET=your-session-secret \\\n  -e BASE_URL=http://localhost:8080 \\\n  authgate\n\n# Check health\ncurl http://localhost:8080/health\n\n# Inspect image labels to verify version\ndocker inspect authgate:v1.0.0 | grep -A 5 Labels\n```\n\n#### Docker Features\n\n- Alpine-based (minimal attack surface)\n- Multi-architecture support (amd64, arm64)\n- Runs as non-root user (UID 1000)\n- Built-in health check endpoint\n- Persistent volume for SQLite database\n- Embedded templates and static files (single binary)\n- Version labels via `--build-arg VERSION=\u003cversion\u003e` (supports both OCI and Label Schema standards)\n\n#### Docker Compose Example\n\n```yaml\nversion: \"3.8\"\n\nservices:\n  authgate:\n    image: authgate:latest\n    container_name: authgate\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - authgate-data:/app/data\n    environment:\n      - BASE_URL=https://auth.yourdomain.com\n      - JWT_SECRET=${JWT_SECRET}\n      - SESSION_SECRET=${SESSION_SECRET}\n      - DATABASE_PATH=/app/data/oauth.db\n    restart: unless-stopped\n    healthcheck:\n      test:\n        [\n          \"CMD\",\n          \"wget\",\n          \"--no-verbose\",\n          \"--tries=1\",\n          \"--spider\",\n          \"http://localhost:8080/health\",\n        ]\n      interval: 30s\n      timeout: 3s\n      retries: 3\n      start_period: 5s\n\nvolumes:\n  authgate-data:\n```\n\n### Test with the Example CLI\n\n```bash\ncd _example/authgate-cli\n\n# Configure the client\ncp .env.example .env\nnano .env  # Add the CLIENT_ID from server logs\n\n# Run the CLI\ngo run main.go\n```\n\nThe CLI demonstrates:\n\n1. **First Run (Device Flow)**:\n   - Requests a device code\n   - Displays a URL and user code\n   - Waits for authorization\n   - Receives access token + refresh token\n   - Saves tokens to `.authgate-tokens.json`\n   - Verifies the token\n\n2. **Subsequent Runs (Token Reuse)**:\n   - Loads existing tokens from file\n   - Uses access token if still valid\n   - Automatically refreshes if expired\n   - Saves refreshed tokens\n\n3. **Automatic Refresh Demo**:\n   - Makes API call with automatic 401 handling\n   - Refreshes token and retries on authentication failure\n\n**Token Storage**: Tokens are securely saved to `.authgate-tokens.json` (excluded from git) with file permissions 0600 (read/write for owner only)\n\n---\n\n## User Interface\n\nAuthGate provides a clean, modern web interface for user authentication and device authorization. Below are screenshots of the complete authorization flow:\n\n### 1. Login Page\n\n![Login Page](images/login-page.png)\n\nUsers are prompted to sign in with their credentials before authorizing any device. The login page features:\n\n- Simple username and password authentication\n- Clear call-to-action: \"Sign in to authorize your device\"\n- Responsive design that works on both desktop and mobile browsers\n\n### 2. Device Authorization Page\n\n![Device Authorization](images/device-page.png)\n\nAfter successful login, users see the device authorization page where they:\n\n- Enter the code displayed on their CLI tool or device\n- See their current logged-in status with a logout option\n- Submit the code with a clear \"Authorize Device\" button\n- Code format: `XXXX-XXXX` (8 characters, case-insensitive)\n\n### 3. Authorization Success\n\n![Authorization Successful](images/authorization-successful.png)\n\nUpon successful authorization, users receive confirmation with:\n\n- Visual success indicator (green checkmark)\n- Confirmation message showing which client was authorized\n- Clear instructions to return to their CLI tool\n- Option to authorize additional devices without re-login\n- Logout button for security\n\n### 4. Session Management\n\nAfter logging in, users can manage their active sessions by clicking the \"Active Sessions\" link on the device authorization page. The session management interface provides:\n\n- **View All Active Sessions** - See all devices that have been authorized with your account\n- **Client Information** - Display client name and ID for easy identification\n- **Session Details** - View creation time, expiration time, and granted scopes\n- **Individual Revocation** - Revoke specific device access with one click\n- **Revoke All** - Sign out all devices simultaneously for security\n- **Status Indicators** - Visual display of active vs. expired sessions\n\nThis feature gives users complete control over which devices can access their account, enhancing security and transparency.\n\n---\n\n## How It Works\n\n### Device Flow Sequence\n\n```mermaid\nsequenceDiagram\n    participant CLI as CLI Tool\n    participant AuthGate as AuthGate Server\n    participant User as User (Browser)\n\n    Note over CLI,User: Phase 1: Device Code Request\n    CLI-\u003e\u003e+AuthGate: POST /oauth/device/code\u003cbr/\u003e(client_id)\n    AuthGate--\u003e\u003e-CLI: device_code, user_code\u003cbr/\u003everification_uri\n\n    Note over CLI: Display to user:\u003cbr/\u003e\"Visit http://..../device\"\u003cbr/\u003e\"Enter code: 12345678\"\n\n    Note over CLI,User: Phase 2: User Authorization\n    User-\u003e\u003e+AuthGate: GET /device\n    AuthGate--\u003e\u003e-User: Login page (if not authenticated)\n\n    User-\u003e\u003e+AuthGate: POST /login\u003cbr/\u003e(username, password)\n    AuthGate--\u003e\u003e-User: Redirect to /device\u003cbr/\u003e(session created)\n\n    User-\u003e\u003e+AuthGate: GET /device\u003cbr/\u003e(show code entry form)\n    AuthGate--\u003e\u003e-User: Code entry page\n\n    User-\u003e\u003e+AuthGate: POST /device/verify\u003cbr/\u003e(user_code: 12345678)\n    AuthGate--\u003e\u003e-User: Success page\n\n    Note over CLI,User: Phase 3: Token Polling\n    CLI-\u003e\u003e+AuthGate: POST /oauth/token\u003cbr/\u003e(device_code, polling)\n    AuthGate--\u003e\u003e-CLI: {\"error\": \"authorization_pending\"}\n\n    Note over CLI: Wait 5 seconds\n\n    CLI-\u003e\u003e+AuthGate: POST /oauth/token\u003cbr/\u003e(device_code, polling)\n    AuthGate--\u003e\u003e-CLI: {\"access_token\": \"eyJ...\",\u003cbr/\u003e\"token_type\": \"Bearer\",\u003cbr/\u003e\"expires_in\": 3600}\n\n    Note over CLI: Authentication complete!\u003cbr/\u003eStore and use access token\n```\n\n### Key Endpoints\n\n| Endpoint                       | Method   | Auth Required | Purpose                                                             |\n| ------------------------------ | -------- | ------------- | ------------------------------------------------------------------- |\n| `/health`                      | GET      | No            | Health check with database connection test                          |\n| `/oauth/device/code`           | POST     | No            | Request device and user codes (CLI/device)                          |\n| `/oauth/token`                 | POST     | No            | Token endpoint (grant_type=device_code or grant_type=refresh_token) |\n| `/oauth/tokeninfo`             | GET      | No            | Verify token validity (pass token as query)                         |\n| `/oauth/revoke`                | POST     | No            | Revoke access token (RFC 7009)                                      |\n| `/device`                      | GET      | Yes (Session) | User authorization page (browser)                                   |\n| `/device/verify`               | POST     | Yes (Session) | Complete authorization (submit user_code)                           |\n| `/account/sessions`            | GET      | Yes (Session) | View all active sessions                                            |\n| `/account/sessions/:id/revoke` | POST     | Yes (Session) | Revoke specific session                                             |\n| `/account/sessions/revoke-all` | POST     | Yes (Session) | Revoke all user sessions                                            |\n| `/login`                       | GET/POST | No            | User login (creates session)                                        |\n| `/logout`                      | GET      | Yes (Session) | User logout (destroys session)                                      |\n\n#### Endpoint Details\n\n##### Device Flow (CLI)\n\n- `POST /oauth/device/code` - Returns `device_code`, `user_code`, `verification_uri`, `interval` (5s)\n- `POST /oauth/token` - Token endpoint supporting multiple grant types:\n  - **Device Code Grant**: `grant_type=urn:ietf:params:oauth:grant-type:device_code`\n    - Poll with `device_code` and `client_id`\n    - Returns `access_token`, `refresh_token`, `token_type`, `expires_in`, `scope`\n    - Returns `authorization_pending` error while waiting for user\n  - **Refresh Token Grant**: `grant_type=refresh_token`\n    - Request with `refresh_token`, `client_id`, and optional `scope`\n    - Returns new `access_token` (fixed mode) or new `access_token` + `refresh_token` (rotation mode)\n    - Returns `invalid_grant` error if refresh token is invalid/expired\n\n##### User Authorization (Browser)\n\n- `GET /device` - Shows code entry form (redirects to `/login` if not authenticated)\n- `POST /device/verify` - Validates and approves user code (requires valid session)\n\n##### Token Validation\n\n- `GET /oauth/tokeninfo?access_token=\u003cJWT\u003e` - Returns token details or error\n\n##### Token Revocation (RFC 7009)\n\n- `POST /oauth/revoke` - Revoke access token (CLI)\n  - Parameters: `token` (required) - The JWT token to revoke\n  - Parameters: `token_type_hint` (optional) - Set to \"access_token\"\n  - Returns: HTTP 200 on success (even if token doesn't exist, per RFC 7009)\n  - Note: Prevents token scanning attacks by always returning success\n\n##### Session Management (Web UI)\n\n- `GET /account/sessions` - View all active sessions for current user\n  - Displays: Client name, Client ID, scopes, creation/expiration times, status\n  - Requires: Valid user session (login required)\n\n- `POST /account/sessions/:id/revoke` - Revoke specific session\n  - Parameters: `:id` - Token ID to revoke\n  - Requires: Valid user session, token must belong to current user\n  - Returns: Redirect to sessions page\n\n- `POST /account/sessions/revoke-all` - Sign out all devices\n  - Revokes all access tokens for the current user\n  - Useful for security incidents or password changes\n  - Returns: Redirect to sessions page\n\n**Security Note:** Session management endpoints use CSRF protection and verify token ownership before revocation.\n\n---\n\n## Configuration\n\n### Environment Variables\n\nCreate a `.env` file in the project root:\n\n```bash\n# Server Configuration\nSERVER_ADDR=:8080                # Listen address (e.g., :8080, 0.0.0.0:8080)\nBASE_URL=http://localhost:8080   # Public URL for verification_uri\n\n# Security - CHANGE THESE IN PRODUCTION!\nJWT_SECRET=your-256-bit-secret-change-in-production       # HMAC-SHA256 signing key\nSESSION_SECRET=session-secret-change-in-production        # Cookie encryption key\n\n# Database\nDATABASE_DRIVER=sqlite           # Database driver: \"sqlite\" or \"postgres\"\nDATABASE_DSN=oauth.db            # Connection string (file path for SQLite, DSN for PostgreSQL)\n\n# PostgreSQL Example:\n# DATABASE_DRIVER=postgres\n# DATABASE_DSN=\"host=localhost user=authgate password=secret dbname=authgate port=5432 sslmode=disable\"\n\n# Authentication Mode\n# Options: local, http_api\n# Default: local\nAUTH_MODE=local\n\n# HTTP API Authentication (when AUTH_MODE=http_api)\nHTTP_API_URL=https://auth.example.com/api/verify\nHTTP_API_TIMEOUT=10s\nHTTP_API_INSECURE_SKIP_VERIFY=false\n\n# HTTP API Retry Configuration\n# Automatic retry with exponential backoff for failed requests\nHTTP_API_MAX_RETRIES=3           # Maximum retry attempts (default: 3, set 0 to disable)\nHTTP_API_RETRY_DELAY=1s          # Initial retry delay (default: 1s)\nHTTP_API_MAX_RETRY_DELAY=10s     # Maximum retry delay (default: 10s)\n\n# Token Provider Mode\n# Options: local, http_api\n# Default: local\nTOKEN_PROVIDER_MODE=local\n\n# HTTP API Token Provider (when TOKEN_PROVIDER_MODE=http_api)\n# External token service will handle JWT generation and validation\nTOKEN_API_URL=https://token.example.com/api\nTOKEN_API_TIMEOUT=10s\nTOKEN_API_INSECURE_SKIP_VERIFY=false\n\n# Token API Retry Configuration\n# Automatic retry with exponential backoff for failed requests\nTOKEN_API_MAX_RETRIES=3          # Maximum retry attempts (default: 3, set 0 to disable)\nTOKEN_API_RETRY_DELAY=1s         # Initial retry delay (default: 1s)\nTOKEN_API_MAX_RETRY_DELAY=10s    # Maximum retry delay (default: 10s)\n\n# Refresh Token Configuration\nREFRESH_TOKEN_EXPIRATION=720h        # Refresh token lifetime (default: 30 days)\nENABLE_REFRESH_TOKENS=true          # Feature flag to enable/disable refresh tokens\nENABLE_TOKEN_ROTATION=false         # Enable rotation mode (default: fixed mode)\n```\n\n#### Generate Strong Secrets\n\n```bash\n# Generate JWT_SECRET (64 characters recommended)\nopenssl rand -hex 32\n\n# Generate SESSION_SECRET (64 characters recommended)\nopenssl rand -hex 32\n\n# Or use this one-liner to update .env\necho \"JWT_SECRET=$(openssl rand -hex 32)\" \u003e\u003e .env\necho \"SESSION_SECRET=$(openssl rand -hex 32)\" \u003e\u003e .env\n```\n\n### Default Test Data\n\nThe server initializes with default test accounts:\n\n#### User Account\n\n- Username: `admin`\n- Password: Auto-generated 16-character random password (shown in server logs on first run)\n\n#### OAuth Client\n\n- Name: `AuthGate CLI`\n- Client ID: Auto-generated UUID (shown in server logs)\n\n**⚠️ Security Warning:** Note the admin password from server logs on first run and change it in production!\n\n### Pluggable Token Providers\n\nAuthGate supports **pluggable token providers** for JWT generation and validation, allowing you to delegate token operations to external services while maintaining local token management.\n\n#### Architecture\n\n- **Token Generation \u0026 Validation**: Can be handled locally or by external HTTP API\n- **Local Storage**: Token records are always stored in local database for management (revocation, listing, auditing)\n- **Configuration**: Global mode selection via `TOKEN_PROVIDER_MODE` environment variable\n\n#### Token Provider Modes\n\n**1. Local Mode (Default)**\n\nUses local JWT secret for token signing and verification:\n\n```bash\nTOKEN_PROVIDER_MODE=local  # Default, can be omitted\n```\n\n- JWT signed with HMAC-SHA256\n- Uses `JWT_SECRET` from environment\n- No external dependencies\n- Best for: Self-contained deployments\n\n**2. HTTP API Mode**\n\nDelegates JWT generation and validation to external service:\n\n```bash\nTOKEN_PROVIDER_MODE=http_api\nTOKEN_API_URL=https://token-service.example.com/api\nTOKEN_API_TIMEOUT=10s\nTOKEN_API_INSECURE_SKIP_VERIFY=false  # Set true only for dev/testing\n```\n\n- External service generates and validates JWTs\n- Local database still stores token records\n- Supports custom signing algorithms (RS256, ES256, etc.)\n- Best for: Centralized token services, advanced key management\n\n#### HTTP API Contract\n\nWhen using `TOKEN_PROVIDER_MODE=http_api`, your token service must implement:\n\n**Token Generation Endpoint:** `POST {TOKEN_API_URL}/generate`\n\nRequest:\n\n```json\n{\n  \"user_id\": \"user-uuid\",\n  \"client_id\": \"client-uuid\",\n  \"scopes\": \"read write\",\n  \"expires_in\": 3600\n}\n```\n\nResponse:\n\n```json\n{\n  \"success\": true,\n  \"access_token\": \"eyJhbGc...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 3600,\n  \"claims\": {\n    \"custom_claim\": \"value\"\n  }\n}\n```\n\n**Token Validation Endpoint:** `POST {TOKEN_API_URL}/validate`\n\nRequest:\n\n```json\n{\n  \"token\": \"eyJhbGc...\"\n}\n```\n\nResponse (Valid):\n\n```json\n{\n  \"valid\": true,\n  \"user_id\": \"user-uuid\",\n  \"client_id\": \"client-uuid\",\n  \"scopes\": \"read write\",\n  \"expires_at\": 1736899200,\n  \"claims\": {\n    \"custom_claim\": \"value\"\n  }\n}\n```\n\nResponse (Invalid):\n\n```json\n{\n  \"valid\": false,\n  \"message\": \"Token expired or invalid\"\n}\n```\n\n#### Why Local Storage is Retained\n\nEven when using external token providers, AuthGate stores token records locally for:\n\n1. **Revocation**: Users can revoke tokens via `/account/sessions` or `/oauth/revoke`\n2. **Management**: Users can list their active sessions\n3. **Auditing**: Track when and for which clients tokens were issued\n4. **Client Association**: Link tokens to OAuth clients\n\n#### Use Cases\n\n**Local Mode:**\n\n- Self-hosted deployments\n- Simple setups\n- When you don't need advanced key management\n\n**HTTP API Mode:**\n\n- Centralized token services across multiple apps\n- Advanced key rotation policies\n- Custom JWT signing algorithms (RS256, ES256)\n- Compliance requirements for token generation\n- Integration with existing IAM systems\n\n#### Migration Path\n\n1. Start with `TOKEN_PROVIDER_MODE=local` (default)\n2. Test thoroughly\n3. Set up external token service\n4. Switch to `TOKEN_PROVIDER_MODE=http_api`\n5. Monitor logs for errors\n6. Can rollback to local mode without data loss\n\n### Service-to-Service Authentication\n\nWhen AuthGate connects to external HTTP APIs (for authentication or token operations), you can secure these service-to-service communications with authentication headers.\n\n#### Why Service-to-Service Authentication?\n\nExternal HTTP API providers (authentication and token services) need to verify that incoming requests are from a trusted AuthGate instance. Without authentication, these endpoints would be vulnerable to unauthorized access.\n\n#### Authentication Modes\n\nAuthGate supports three authentication modes for securing HTTP API communications:\n\n**1. None Mode (Default)**\n\nNo authentication headers are added. Suitable for development or when the external API is secured by other means (e.g., network isolation).\n\n```bash\n# No configuration needed - this is the default\nHTTP_API_AUTH_MODE=none\nTOKEN_API_AUTH_MODE=none\n```\n\n**2. Simple Mode**\n\nAdds a shared secret in a custom header (default: `X-API-Secret`). Quick to set up but less secure than HMAC.\n\n```bash\n# HTTP API Authentication\nHTTP_API_AUTH_MODE=simple\nHTTP_API_AUTH_SECRET=your-shared-secret-here\nHTTP_API_AUTH_HEADER=X-API-Secret  # Optional, default shown\n\n# Token API Authentication\nTOKEN_API_AUTH_MODE=simple\nTOKEN_API_AUTH_SECRET=your-token-secret-here\nTOKEN_API_AUTH_HEADER=X-API-Secret  # Optional, default shown\n```\n\n**3. HMAC Mode (Recommended)**\n\nUses HMAC-SHA256 signature with timestamp validation to prevent replay attacks. Provides the highest security for production environments.\n\n```bash\n# HTTP API Authentication\nHTTP_API_AUTH_MODE=hmac\nHTTP_API_AUTH_SECRET=your-hmac-secret-here\n\n# Token API Authentication\nTOKEN_API_AUTH_MODE=hmac\nTOKEN_API_AUTH_SECRET=your-hmac-token-secret\n```\n\nHMAC mode automatically adds these headers to each request:\n\n- `X-Signature`: HMAC-SHA256 signature of `timestamp + method + path + body`\n- `X-Timestamp`: Unix timestamp (validated within 5-minute window)\n- `X-Nonce`: Unique request identifier\n\n#### Configuration per Service\n\nAuthentication is configured **separately** for each external service:\n\n| Environment Variable    | Purpose                           | Service       |\n| ----------------------- | --------------------------------- | ------------- |\n| `HTTP_API_AUTH_MODE`    | Auth mode for user authentication | HTTP API Auth |\n| `HTTP_API_AUTH_SECRET`  | Shared secret for authentication  | HTTP API Auth |\n| `HTTP_API_AUTH_HEADER`  | Custom header name (simple mode)  | HTTP API Auth |\n| `TOKEN_API_AUTH_MODE`   | Auth mode for token operations    | Token API     |\n| `TOKEN_API_AUTH_SECRET` | Shared secret for token API       | Token API     |\n| `TOKEN_API_AUTH_HEADER` | Custom header name (simple mode)  | Token API     |\n\n#### Server-Side Verification Example\n\nYour external API must verify incoming requests. Here's a Go example for HMAC verification:\n\n```go\nimport httpclient \"github.com/appleboy/go-httpclient\"\n\n// Initialize auth config (server side)\nauthConfig := httpclient.NewAuthConfig(\"hmac\", \"your-hmac-secret\")\n\n// Verify incoming request\nerr := authConfig.VerifyHMACSignature(req, 5*time.Minute)\nif err != nil {\n    http.Error(w, \"Authentication failed\", http.StatusUnauthorized)\n    return\n}\n```\n\n#### Example: Securing External Authentication API\n\n**Scenario**: Your company has a central authentication service that AuthGate should use for user login.\n\n**Setup**:\n\n1. Configure AuthGate to use external authentication with HMAC:\n\n```bash\n# .env file\nAUTH_MODE=http_api\nHTTP_API_URL=https://auth.company.com/api/verify\nHTTP_API_AUTH_MODE=hmac\nHTTP_API_AUTH_SECRET=shared-secret-between-services\n```\n\n2. Your authentication API validates the HMAC signature before processing login requests.\n\n3. When users log into AuthGate, their credentials are forwarded to your API with HMAC signature verification.\n\n### HTTP Retry with Exponential Backoff\n\nAuthGate includes automatic HTTP retry capabilities for all external API communications (authentication and token operations) to improve reliability and resilience against transient network failures.\n\n#### Features\n\n- **Automatic Retries**: Failed HTTP requests are automatically retried with configurable attempts\n- **Exponential Backoff**: Retry delays increase exponentially to avoid overwhelming failing services\n- **Smart Retry Logic**: Only retries on appropriate errors (network failures, 5xx errors, 429 rate limits)\n- **Non-Blocking**: Retries respect context cancellation and timeouts\n\n#### Default Behavior\n\nBy default, AuthGate retries failed requests up to 3 times with the following pattern:\n\n- **Initial delay**: 1 second\n- **Maximum delay**: 10 seconds\n- **Multiplier**: 2.0x (exponential backoff)\n\nExample retry sequence:\n\n1. First attempt fails → wait 1s\n2. Second attempt fails → wait 2s\n3. Third attempt fails → wait 4s\n4. Fourth attempt fails → return error\n\n#### Automatic Retry Conditions\n\nRequests are automatically retried on:\n\n- Network errors (connection failures, timeouts, DNS issues)\n- HTTP 5xx server errors (500, 502, 503, 504)\n- HTTP 429 (Too Many Requests)\n\nRequests are **not** retried on:\n\n- HTTP 4xx client errors (except 429)\n- HTTP 2xx/3xx successful responses\n- Context cancellation or timeout\n\n#### Configuration\n\nConfigure retry behavior for each external service independently:\n\n**HTTP API Authentication:**\n\n```bash\nHTTP_API_MAX_RETRIES=5              # Maximum retry attempts (default: 3)\nHTTP_API_RETRY_DELAY=2s             # Initial retry delay (default: 1s)\nHTTP_API_MAX_RETRY_DELAY=30s        # Maximum retry delay (default: 10s)\n```\n\n**Token API:**\n\n```bash\nTOKEN_API_MAX_RETRIES=5             # Maximum retry attempts (default: 3)\nTOKEN_API_RETRY_DELAY=2s            # Initial retry delay (default: 1s)\nTOKEN_API_MAX_RETRY_DELAY=30s       # Maximum retry delay (default: 10s)\n```\n\n#### Disable Retries\n\nTo disable retries (not recommended for production):\n\n```bash\nHTTP_API_MAX_RETRIES=0\nTOKEN_API_MAX_RETRIES=0\n```\n\n#### Use Cases\n\n**1. Handling Transient Network Issues**\n\nTemporary network glitches are automatically handled without failing the entire request:\n\n- Brief network interruptions\n- DNS resolution delays\n- Connection pool exhaustion\n\n**2. Service Restarts**\n\nWhen external services restart, AuthGate automatically retries until the service is available:\n\n- Rolling deployments\n- Service updates\n- Container restarts\n\n**3. Rate Limiting**\n\nWhen external APIs return 429 (rate limit), AuthGate backs off and retries:\n\n- Automatic backoff on rate limits\n- Prevents cascading failures\n- Respects service quotas\n\n#### Best Practices\n\n1. **Production Settings**: Use default retry settings (3 retries) for most production scenarios\n2. **High-Traffic Environments**: Consider increasing `MAX_RETRY_DELAY` to 30s-60s to avoid overwhelming recovering services\n3. **Low-Latency Requirements**: Reduce `MAX_RETRIES` to 1-2 for time-sensitive operations\n4. **Monitoring**: Track retry rates to identify unreliable external services\n5. **Timeouts**: Ensure `HTTP_API_TIMEOUT` and `TOKEN_API_TIMEOUT` are set appropriately to account for retries\n\n#### Example: Aggressive Retry Configuration\n\nFor critical services where availability is paramount:\n\n```bash\n# Retry up to 10 times with longer delays\nHTTP_API_MAX_RETRIES=10\nHTTP_API_RETRY_DELAY=500ms\nHTTP_API_MAX_RETRY_DELAY=60s\nHTTP_API_TIMEOUT=120s  # Increase timeout to accommodate retries\n```\n\n#### Example: Conservative Retry Configuration\n\nFor fast-fail scenarios where latency matters more than resilience:\n\n```bash\n# Retry only once with short delays\nHTTP_API_MAX_RETRIES=1\nHTTP_API_RETRY_DELAY=500ms\nHTTP_API_MAX_RETRY_DELAY=2s\nHTTP_API_TIMEOUT=15s\n```\n\n#### Implementation Details\n\n- Built using [go-httpretry v0.2.0](https://github.com/appleboy/go-httpretry)\n- Retry logic wraps the authentication-enabled HTTP client\n- All authentication headers (Simple, HMAC) are preserved across retries\n- Request bodies are cloned for retries to avoid consumed stream issues\n\n---\n\n## AuthGate Architecture\n\n### Project Structure\n\n```txt\nauthgate/\n├── config/          # Configuration management (environment variables, defaults)\n├── handlers/        # HTTP request handlers\n│   ├── auth.go      # User login/logout endpoints\n│   ├── device.go    # Device authorization flow (/device, /device/verify)\n│   ├── token.go     # Token issuance (/oauth/token), verification (/oauth/tokeninfo), and revocation (/oauth/revoke)\n│   ├── session.go   # Session management (/account/sessions)\n│   └── client.go    # Admin client management\n├── middleware/      # HTTP middleware\n│   ├── auth.go      # Session authentication (RequireAuth, RequireAdmin)\n│   └── csrf.go      # CSRF protection middleware\n├── models/          # Data models\n│   ├── user.go      # User accounts\n│   ├── client.go    # OAuth clients (OAuthClient)\n│   ├── device.go    # Device codes (DeviceCode)\n│   └── token.go     # Access tokens (AccessToken)\n├── auth/            # Authentication providers (pluggable design)\n│   ├── local.go     # Local authentication (database)\n│   └── http_api.go  # External HTTP API authentication\n├── token/           # Token providers (pluggable design)\n│   ├── types.go     # Shared data structures (TokenResult, TokenValidationResult)\n│   ├── errors.go    # Provider-level error definitions\n│   ├── local.go     # Local JWT provider (HMAC-SHA256)\n│   └── http_api.go  # External HTTP API token provider\n├── services/        # Business logic layer (depends on store and providers)\n│   ├── user.go      # User management (integrates auth providers)\n│   ├── device.go    # Device code generation and validation\n│   ├── token.go     # Token service (integrates token providers)\n│   └── client.go    # OAuth client management\n├── store/           # Database layer (GORM)\n│   ├── driver.go    # Database driver factory (SQLite, PostgreSQL)\n│   └── sqlite.go    # Database initialization, migrations, seed data, batch queries\n├── templates/       # HTML templates (embedded via go:embed)\n│   ├── account/     # User account templates\n│   │   └── sessions.html  # Active sessions management page\n│   └── admin/       # Admin panel templates\n├── static/          # Static files (embedded via go:embed)\n├── docker/          # Docker configuration\n│   └── Dockerfile   # Alpine-based multi-arch image\n├── _example/        # Example CLI client implementation\n│   └── authgate-cli/\n├── version/         # Version information (embedded at build time)\n├── Makefile         # Build automation and targets\n├── main.go          # Application entry point and router setup\n├── .env.example     # Environment configuration template\n└── CLAUDE.md        # AI assistant guidance (optional)\n```\n\n### Technology Stack\n\n- **Web Framework:** [Gin](https://gin-gonic.com/) - Fast HTTP router\n- **ORM:** [GORM](https://gorm.io/) - Database abstraction\n- **Database:** SQLite - Embedded database\n- **Sessions:** [gin-contrib/sessions](https://github.com/gin-contrib/sessions) - Cookie sessions\n- **JWT:** [golang-jwt/jwt](https://github.com/golang-jwt/jwt) - Token generation\n- **Config:** [joho/godotenv](https://github.com/joho/godotenv) - Environment management\n\n---\n\n## Development\n\n### Build Commands\n\n```bash\n# Build binary with version info (outputs to bin/authgate)\nmake build\n\n# Install binary to $GOPATH/bin\nmake install\n\n# Run tests with coverage report (generates coverage.txt)\nmake test\n\n# Run linter (auto-installs golangci-lint if missing)\nmake lint\n\n# Format code with golangci-lint\nmake fmt\n\n# Cross-compile for Linux\nmake build_linux_amd64  # Static binary (CGO_ENABLED=0)\nmake build_linux_arm64  # Static binary (CGO_ENABLED=0)\n\n# Clean build artifacts and coverage\nmake clean\n\n# Show all available targets\nmake help\n```\n\n#### Build Details\n\n- Version information is automatically embedded using git tags/commits\n- LDFLAGS includes: Version, BuildTime, GitCommit, GoVersion, BuildOS, BuildArch\n- Cross-compiled binaries are statically linked (no external dependencies)\n- Output locations: `bin/` for local builds, `release/\u003cos\u003e/\u003carch\u003e/` for cross-compilation\n\n### Database Schema\n\nThe application automatically creates these tables:\n\n- `users` - User accounts\n- `oauth_clients` - Registered client applications\n- `device_codes` - Active device authorization requests\n- `access_tokens` - Issued JWT tokens\n\n### Extending the Server\n\n#### Add a new OAuth client\n\n```go\nclient := \u0026models.OAuthClient{\n    Name:         \"My App\",\n    ClientID:     uuid.New().String(),\n    RedirectURIs: \"http://localhost:3000/callback\",\n}\ndb.Create(client)\n```\n\n#### Add custom scopes\n\nModify `services/device.go` to validate and store additional scopes.\n\n---\n\n## Monitoring and Observability\n\n### Health Check Endpoint\n\n```bash\n# Basic health check\ncurl http://localhost:8080/health\n\n# Response format (JSON)\n{\n  \"status\": \"healthy\",\n  \"database\": \"connected\",\n  \"timestamp\": \"2026-01-07T10:00:00Z\"\n}\n```\n\n#### Health Check Details\n\n- Tests database connectivity with a ping\n- Returns HTTP 200 on success, 503 on database failure\n- Used by Docker HEALTHCHECK directive\n- Recommended monitoring interval: 30 seconds\n\n### Monitoring Best Practices\n\n#### Key Metrics to Monitor\n\n- Health check endpoint availability\n- Database file size growth\n- Active device codes count\n- Issued tokens per hour\n- Session count\n- HTTP response times\n- Failed login attempts\n\n#### Logging\n\n- Gin framework logs all HTTP requests\n- Include request ID for tracing\n- Log authentication failures for security monitoring\n\n---\n\n## Security Considerations\n\n### Production Deployment Checklist\n\n- [ ] Change `JWT_SECRET` to a strong random value (32+ characters)\n- [ ] Change `SESSION_SECRET` to a strong random value (32+ characters)\n- [ ] Use HTTPS (set `BASE_URL` to `https://...`)\n- [ ] Change default admin user password (check server logs for initial random password)\n- [ ] Set appropriate `DeviceCodeExpiration` (default: 30 minutes)\n- [ ] Set appropriate `JWTExpiration` (default: 1 hour)\n- [ ] Configure firewall rules\n- [ ] Enable rate limiting for token polling and revocation endpoints\n- [ ] Regularly backup `oauth.db`\n- [ ] Set up automated cleanup for expired tokens and device codes\n- [ ] Use Docker non-root user mode (already configured)\n- [ ] Configure timeouts for HTTP server (ReadTimeout, WriteTimeout)\n- [ ] Enable CORS policies if needed\n- [ ] Monitor `/health` endpoint for service availability\n- [ ] Educate users to use `/account/sessions` to review and revoke suspicious devices\n\n### Threat Model\n\n#### What AuthGate Protects Against\n\n- ✅ Client secret exposure in distributed apps\n- ✅ Phishing attacks (user authorizes on trusted domain)\n- ✅ Replay attacks (device codes are single-use)\n- ✅ Token tampering (JWT signature verification)\n\n#### What You Must Secure\n\n- 🔒 Server host security\n- 🔒 Database encryption at rest\n- 🔒 TLS/HTTPS in production\n- 🔒 Secret rotation policies\n\n---\n\n## Deployment\n\n### Production Deployment Options\n\n#### 1. Binary Deployment (Systemd)\n\n```bash\n# Build static binary\nmake build_linux_amd64\n\n# Copy to server\nscp release/linux/amd64/authgate user@server:/usr/local/bin/\n\n# Create systemd service\ncat \u003e /etc/systemd/system/authgate.service \u003c\u003cEOF\n[Unit]\nDescription=AuthGate OAuth Server\nAfter=network.target\n\n[Service]\nType=simple\nUser=authgate\nWorkingDirectory=/var/lib/authgate\nExecStart=/usr/local/bin/authgate server\nRestart=on-failure\nRestartSec=10\n\n# Security\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/authgate\n\n# Environment\nEnvironmentFile=/etc/authgate/.env\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n# Enable and start\nsystemctl enable authgate\nsystemctl start authgate\n```\n\n#### 2. Docker Deployment\n\n```bash\n# Build with version information\nVERSION=$(git describe --tags --always --dirty)\ndocker build -f docker/Dockerfile \\\n  --build-arg VERSION=${VERSION} \\\n  -t authgate:${VERSION} \\\n  -t authgate:latest \\\n  .\n\n# Using Docker Compose (recommended)\ndocker-compose up -d\n\n# Or standalone Docker\ndocker run -d \\\n  --name authgate \\\n  --restart unless-stopped \\\n  -p 8080:8080 \\\n  -v /var/lib/authgate:/app/data \\\n  -e JWT_SECRET=$(openssl rand -hex 32) \\\n  -e SESSION_SECRET=$(openssl rand -hex 32) \\\n  -e BASE_URL=https://auth.yourdomain.com \\\n  authgate:latest\n\n# Verify deployed version\ndocker inspect authgate:latest --format '{{index .Config.Labels \"org.opencontainers.image.version\"}}'\n```\n\n#### 3. Reverse Proxy Setup (Nginx)\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name auth.yourdomain.com;\n\n    ssl_certificate /etc/letsencrypt/live/auth.yourdomain.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/auth.yourdomain.com/privkey.pem;\n\n    location / {\n        proxy_pass http://localhost:8080;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        # WebSocket support (if needed)\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n    }\n}\n```\n\n#### 4. Cloud Platform Deployment\n\n##### Fly.io Example\n\n```bash\n# Install flyctl\ncurl -L https://fly.io/install.sh | sh\n\n# Launch app\nfly launch\n\n# Set secrets\nfly secrets set JWT_SECRET=$(openssl rand -hex 32)\nfly secrets set SESSION_SECRET=$(openssl rand -hex 32)\n\n# Deploy\nfly deploy\n```\n\n---\n\n## Use Cases Sample\n\n### Example: Securing a CLI Tool\n\nYour CLI tool needs to access a protected API:\n\n1. **Server Setup:** Deploy AuthGate with your custom client\n2. **CLI Integration:** Use the device flow to get user tokens\n3. **API Calls:** Include the JWT in `Authorization: Bearer \u003ctoken\u003e` headers\n4. **Token Refresh:** Store tokens securely, implement refresh logic\n\n### Example: IoT Device Authentication\n\nYour smart device needs user authorization:\n\n1. Device displays a short code on its screen\n2. User visits the URL on their phone\n3. User logs in and enters the code\n4. Device receives token and can now access user's account\n5. Token stored securely in device memory\n\n### Example: Security Incident Response\n\nWhen a user suspects unauthorized access:\n\n1. **User logs in** to the web interface\n2. **Reviews active sessions** at `/account/sessions`\n3. **Identifies suspicious devices** by checking client names and authorization times\n4. **Revokes specific sessions** for unrecognized devices\n5. **Or revokes all sessions** if multiple devices are compromised\n6. **Re-authorizes legitimate devices** after security review\n\nThis workflow gives users complete control and visibility over their device authorizations, meeting modern security and privacy expectations.\n\n---\n\n## Performance Considerations\n\n### Scalability\n\n#### Current Architecture (SQLite)\n\n- Suitable for: Small to medium deployments (\u003c 1000 concurrent devices)\n- Limitations: SQLite write locks can cause contention under heavy load\n- Recommended: Monitor database file size and query performance\n\n#### For High-Scale Deployments\n\nAuthGate now supports PostgreSQL natively. Simply configure via environment variables:\n\n```bash\n# .env configuration\nDATABASE_DRIVER=postgres\nDATABASE_DSN=\"host=localhost user=authgate password=secret dbname=authgate port=5432 sslmode=require\"\n```\n\nNo code changes required! The application automatically selects the appropriate driver based on your configuration.\n\n#### Performance Tips\n\n- Enable SQLite WAL mode for better concurrent read performance\n- Add indexes on frequently queried columns (`device_code`, `user_code`, `client_id`)\n- Implement connection pooling for PostgreSQL\n- Use Redis for session storage instead of cookies\n- Add caching layer for token validation\n- Clean up expired device codes and tokens regularly\n- **Batch Queries:** Session management uses `WHERE IN` queries to avoid N+1 problems when fetching client information\n\n### Benchmarks (Reference)\n\n**Hardware:** 2-core CPU, 4GB RAM, SSD\n**Test:** 100 concurrent device authorization flows\n\n| Metric               | SQLite | PostgreSQL |\n| -------------------- | ------ | ---------- |\n| Requests/sec         | ~500   | ~2000      |\n| Avg Response Time    | 20ms   | 5ms        |\n| P95 Response Time    | 50ms   | 15ms       |\n| Database Size (1000) | 2MB    | 5MB        |\n\n---\n\n## Comparison with Other Solutions\n\n| Feature          | AuthGate      | Auth0  | Keycloak     | Custom OAuth |\n| ---------------- | ------------- | ------ | ------------ | ------------ |\n| Device Flow      | ✅            | ✅     | ✅           | 🔧 DIY       |\n| Self-Hosted      | ✅            | ❌     | ✅           | ✅           |\n| Lightweight      | ✅ (\u003c 20MB)   | N/A    | ❌ (\u003e 500MB) | 🔧 Varies    |\n| Setup Time       | 5 min         | 15 min | 1 hour       | Days         |\n| Learning Curve   | Low           | Medium | High         | High         |\n| Cost             | Free (OSS)    | $$$    | Free (OSS)   | Dev Time     |\n| Production Ready | ✅ (w/ audit) | ✅     | ✅           | 🔧 Varies    |\n| Multi-tenancy    | ❌ (DIY)      | ✅     | ✅           | 🔧 DIY       |\n| Embedded Binary  | ✅            | N/A    | ❌           | 🔧 Varies    |\n\n---\n\n## Troubleshooting\n\n### Common Issues\n\n#### Issue: \"Client not found\" error\n\n```bash\n# Solution: Check that CLIENT_ID in your CLI .env matches the server logs\n# Server logs show: \"Seeded OAuth client with ID: abc-123-def\"\n```\n\n#### Issue: Database locked errors\n\n```bash\n# Solution: Ensure only one instance is running, or use WAL mode\n# SQLite doesn't handle high concurrency well - consider PostgreSQL for production\n```\n\n#### Issue: \"authorization_pending\" never resolves\n\n```bash\n# Solution: Check that the user completed authorization in browser\n# Verify the user_code was entered correctly (case-insensitive, dashes ignored)\n# Check server logs for errors during authorization\n```\n\n#### Issue: \"Username conflict with existing user\" error\n\n**Problem**: User sees \"Username conflict with existing user. Please contact administrator.\" when logging in via external API.\n\n**Cause**: The username returned by the external authentication API matches an existing user in the local database.\n\n**Resolution Options**:\n\n1. **Rename existing local user** (if it's a different person):\n\n   ```sql\n   UPDATE users SET username = 'newname' WHERE username = 'conflicting-username';\n   ```\n\n2. **Configure external API to use different username**: Update the external system to return a unique username (e.g., append domain suffix).\n\n3. **Manual account merge** (if it's the same person):\n   - Ensure the local user has `auth_source='http_api'` and correct `external_id`\n   - Recommended for migrating local users to external authentication\n\n**Prevention**: Use namespaced usernames in external API (e.g., [john@company.com](mailto:john@company.com) instead of \"john\").\n\n#### Issue: JWT signature verification fails\n\n```bash\n# Solution: Ensure JWT_SECRET is the same across restarts\n# Don't change JWT_SECRET while tokens are still valid\n```\n\n#### Issue: Session not persisting\n\n```bash\n# Solution: Ensure SESSION_SECRET is set\n# Check that cookies are enabled in browser\n# Verify BASE_URL matches the domain you're accessing\n```\n\n### Debug Mode\n\nEnable debug logging by setting Gin to debug mode:\n\n```bash\nGIN_MODE=debug ./bin/authgate server\n```\n\n---\n\n## FAQ\n\n### Q: Why not use OAuth password grant?\n\nA: Password grant requires users to enter credentials directly into your app, which trains users to trust third parties with passwords (security anti-pattern).\n\n### Q: Can I use this in production?\n\nA: Yes, but ensure you follow the security checklist and harden the deployment. This is a reference implementation - audit it for your specific needs.\n\n### Q: How do I add user registration?\n\nA: Implement registration handlers in `handlers/auth.go` and update the database schema in `models/user.go`.\n\n### Q: Can I use this with multiple clients?\n\nA: Yes! Add additional clients to the `oauth_clients` table with unique `client_id` values. Each client can have different redirect URIs.\n\n### Q: What about token refresh?\n\nA: AuthGate now fully supports refresh tokens (RFC 6749) with two modes:\n\n- **Fixed Mode (Default)**: Refresh tokens are reusable, perfect for multi-device scenarios. Each device gets its own refresh token that remains valid until manually revoked or expired. Users can manage all tokens via `/account/sessions`.\n\n- **Rotation Mode**: High-security mode where each refresh returns new tokens and old ones are revoked. Enable with `ENABLE_TOKEN_ROTATION=true`.\n\n**Usage Example:**\n\n```bash\n# Initial authorization returns both tokens\nPOST /oauth/token\n  grant_type=urn:ietf:params:oauth:grant-type:device_code\n  device_code=xxx\n  client_id=xxx\n→ Returns: access_token + refresh_token\n\n# When access token expires, refresh it\nPOST /oauth/token\n  grant_type=refresh_token\n  refresh_token=xxx\n  client_id=xxx\n→ Returns: new access_token (fixed mode) or access_token + refresh_token (rotation mode)\n```\n\nConfigure via environment variables:\n\n- `REFRESH_TOKEN_EXPIRATION=720h` (default: 30 days)\n- `ENABLE_REFRESH_TOKENS=true` (default)\n- `ENABLE_TOKEN_ROTATION=false` (default: fixed mode)\n\n### Q: How do users revoke device access?\n\nA: Users have multiple options to revoke access:\n\n- **Web UI:** Visit `/account/sessions` to view and revoke individual devices\n- **CLI/API:** Call `POST /oauth/revoke` with the token parameter (RFC 7009)\n- **Revoke All:** Use the \"Revoke All\" button on the sessions page to sign out all devices at once\n\n### Q: How long do device codes last?\n\nA: Device codes expire after 30 minutes by default. This is configurable via `Config.DeviceCodeExpiration`.\n\n### Q: Can I use a different database?\n\nA: Yes! AuthGate supports both SQLite and PostgreSQL out of the box:\n\n**SQLite** (default):\n\n```bash\nDATABASE_DRIVER=sqlite\nDATABASE_DSN=oauth.db\n```\n\n**PostgreSQL**:\n\n```bash\nDATABASE_DRIVER=postgres\nDATABASE_DSN=\"host=localhost user=authgate password=secret dbname=authgate port=5432 sslmode=disable\"\n```\n\nThe database driver uses a factory pattern and can be extended to support MySQL or other databases. See `internal/store/driver.go` for implementation details.\n\n### Q: How do I change the polling interval?\n\nA: The polling interval is 5 seconds by default (RFC 8628 compliant). Modify `Config.PollingInterval` in `config/config.go`.\n\n### Q: Are user codes case-sensitive?\n\nA: No, user codes are normalized to uppercase and dashes are removed before lookup (e.g., \"ABCD-1234\" = \"abcd1234\" = \"ABCD1234\").\n\n---\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n---\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n---\n\n## References\n\n- [RFC 8628 - OAuth 2.0 Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628)\n- [RFC 6749 - OAuth 2.0 Framework (Refresh Tokens)](https://datatracker.ietf.org/doc/html/rfc6749)\n- [RFC 7009 - OAuth 2.0 Token Revocation](https://datatracker.ietf.org/doc/html/rfc7009)\n- [OAuth 2.0 Documentation](https://oauth.net/2/)\n- [JWT Best Practices](https://datatracker.ietf.org/doc/html/rfc8725)\n\n---\n\n## Acknowledgments\n\nBuilt with ❤️ using:\n\n- [Gin Web Framework](https://gin-gonic.com/)\n- [GORM](https://gorm.io/)\n- [golang-jwt](https://github.com/golang-jwt/jwt)\n\n---\n\n**Need Help?** Open an issue or check the `_example/` directory for working client code.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappleboy%2Fauthgate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappleboy%2Fauthgate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappleboy%2Fauthgate/lists"}