A JWT-based authentication system with access tokens and refresh tokens, built in Go with SQLite.
Warning: This is a proof of concept and not production ready.
- User registration with Argon2 password hashing
- User login issuing access tokens (JWT) and refresh tokens
- JWT access token generation using ECDSA P-256 signing
- Refresh token storage with SHA256 hashing
- Token refresh endpoint to exchange refresh tokens for new access tokens
- Protected endpoints requiring JWT authentication
- JWKS endpoint for public key distribution
- User CRUD operations
- Healthcheck endpoint
- Go 1.25+
- SQLite database
- go-jose/v4 for JWT with ECDSA support
- go-crypt for Argon2 password hashing
- slog for structured logging
POST /api/login- Authenticate user, returns access token and refresh tokenPOST /api/refresh- Exchange refresh token for new access token
GET /api/protected/data- Returns protected user dataGET /api/protected/stats- Returns user statistics
GET /api/users- List all users (limit 100)POST /api/users- Create a new userGET /api/users/{id}- Get user by IDDELETE /api/users/{id}- Delete user by ID
GET /health- Health check endpointGET /api/jwks.json- JSON Web Key Set for public key distribution
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE refresh_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner_id TEXT NOT NULL,
hash TEXT NOT NULL,
issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
);- Access Tokens: JWT tokens with 24 hour expiry for API authentication
- Refresh Tokens: Random tokens with 30 day expiry for obtaining new access tokens
- ECDSA P-256: JWT signing algorithm
- Argon2: Password hashing (RFC 9106 low memory profile)
- SHA-256: Refresh token hashing before storage
- crypto/rand: Cryptographically secure random token generation
- Passwords hashed with Argon2 before storage
- Refresh tokens hashed before database storage
- JWT signature verification on protected endpoints
- Token expiry validation
- Structured error responses
- Go 1.25 or higher
# Clone the repository
git clone https://github.com/Crowley723/jwt-auth-poc.git
cd jwt-auth-poc
# Run the server
go run ./main.goThe server starts on http://localhost:8080. On first run:
- Generates ECDSA key pair (stored in
./app/certs/) - Creates SQLite database (stored in
./app/data/app.db) - Runs database migrations
Create a user:
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","name":"John Doe","password":"password123"}'Login:
curl -X POST http://localhost:8080/api/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'Response:
{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "a1b2c3d4e5f6...",
"refresh_token_expiry": 1701648000
}Access protected endpoint:
curl http://localhost:8080/api/protected/data \
-H "Authorization: Bearer <access_token>"Refresh access token:
curl -X POST http://localhost:8080/api/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"<refresh_token>"}'See LICENSE