A production-minded authentication starter for Go teams who want secure defaults without a giant framework.
Build a REST API with Gin, PostgreSQL, OAuth, email verification, password reset, and a security-focused token lifecycle—ready to extend from a clean hexagonal architecture.
- Argon2id passwords — memory-hard password hashing with explicit parameters.
- JWT access tokens — short-lived tokens for authenticated API requests.
- Opaque refresh tokens — cryptographically random values with only HMAC-SHA-256 digests stored in PostgreSQL.
- Refresh rotation and replay detection — one-time refresh tokens with family-wide revocation.
- Secure cookies — HttpOnly, SameSite=Lax, and Secure in production.
- OAuth login — GitHub and Google providers.
- Email workflows — verification and password-reset flows.
- Integration-ready CI — PostgreSQL-backed tests run separately from the unit suite.
- Go 1.25+
- Docker and Docker Compose
- PostgreSQL, or the included Compose database
migrateCLI for integration tests and migrations
git clone https://github.com/Jonathan0823/auth-go.git
cd auth-go
cp .env.example .env
go mod download
docker compose up -d db redis
make migrate-up
make runThe API starts on http://localhost:8080 by default.
With ENABLE_SWAGGER=true outside production, interactive API documentation is available at http://localhost:8080/swagger/index.html. The generated Swagger 2.0 document is served at /swagger/doc.json.
The application uses one PostgreSQL connection string everywhere: DATABASE_URL.
Start from .env.example and set at least:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/auth_go?sslmode=disable
JWT_ACCESS_SECRET=replace-with-a-long-random-secret
REFRESH_TOKEN_HASH_KEY=replace-with-a-separate-long-random-secret
ENABLE_SWAGGER=true
ENABLE_METRICS=false
SESSION_SECRET=replace-with-a-long-random-secretOAuth and email variables are optional until those features are enabled:
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
EMAIL=
PASSWORD=Authentication rate limiting supports memory, redis, and postgres backends. Memory is intended for development or an explicitly approved single-instance deployment; production should use Redis or PostgreSQL. Set RATE_LIMIT_KEY to a separate secret and configure TRUSTED_PROXIES only for known proxy networks.
make loads .env automatically. Use another file explicitly when needed:
ENV_FILE=.env.prod make runmake migrate-up # Apply migrations
make test # Unit tests
make integration # Migrate + PostgreSQL/Redis integration tests
make coverage # Integration tests + coverage/coverage.out
make vet # Static checks
make sqlc # Regenerate PostgreSQL code
make swagger # Regenerate Swagger artifacts
make swagger-validate # Validate the Swagger documentIntegration tests use the integration build tag and require PostgreSQL. Redis-backed tests run when REDIS_ADDR is configured:
go test -tags=integration ./...GET /health/livereports process liveness without checking PostgreSQL.GET /health/readyreports database readiness with a bounded PostgreSQL ping.GET /metricsexposes Prometheus metrics only whenENABLE_METRICS=true; it is an operational endpoint and is intentionally excluded from Swagger.- Security audit events are emitted as structured JSON logs with request IDs and safe categorical context.
- Rate-limit denials and backend failures use low-cardinality audit and Prometheus events without raw identifiers.
Metrics labels use route templates and avoid user-controlled values. Restrict /metrics to trusted monitoring systems in production. Grafana, Loki, and tracing infrastructure are intentionally not bundled.
- A successful login returns a short-lived JWT access token and an opaque refresh token in secure cookies.
- Refresh tokens are random bearer values; no user data or JWT claims are embedded in them.
- Only an HMAC digest of a refresh token is persisted.
- Every refresh invalidates the previous token and issues a replacement.
- Reusing a rotated token revokes the entire refresh-token family.
- Access tokens remain stateless and expire after 15 minutes.
Migration note: The security migration invalidates existing JWT refresh sessions. Legacy bcrypt password hashes are not accepted; affected users must reset their password.
Swagger documentation is generated from Go annotations and committed under docs/. To regenerate it after changing a handler contract:
make swagger
make swagger-validateSwagger UI is opt-in and development-only. Set ENABLE_SWAGGER=true locally; it remains disabled whenever ENVIRONMENT=production.
POST /api/auth/registerPOST /api/auth/loginPOST /api/auth/refreshPOST /api/auth/logoutPOST /api/auth/forgot-passwordPOST /api/auth/reset-passwordGET /api/auth/verify/emailPOST /api/auth/verify/email/resend
GET /api/oauth/:provider/GET /api/oauth/:provider/callback
Supported providers: GitHub and Google.
GET /api/user/meGET /api/user/:idGET /api/user/get-allGET /api/user/emailPATCH /api/user/updateDELETE /api/user/delete/:id
cmd/ application entrypoint
docs/ generated Swagger specification and registration
internal/
core/ domain models, ports, and services
adapter/ HTTP, PostgreSQL, JWT, password, OAuth, and email adapters
platform/ configuration, database, logging, and server setup
migrations/ PostgreSQL schema migrations
MIT — see LICENSE.