Token Issuer Service
A secure, lightweight OAuth2-compliant token issuing service for the SuperApp ecosystem. This service handles JWT token generation and signing for service-to-service authentication between microapp backends and the SuperApp platform.
Overview
The Token Issuer Service is a critical component of the SuperApp microservices architecture. It provides:
- Service Authentication: Issues JWT tokens for microapp backends to authenticate with SuperApp APIs
- User Context Tokens: Generates tokens that carry user identity for microapp frontends
- JWKS Publishing: Exposes public keys for token validation by downstream services
What This Service Does
| Function | Description |
|---|---|
| Token Issuance | Signs and issues JWTs using RS256 algorithm |
| Client Credentials Grant | OAuth2 flow for service-to-service auth |
| User Context Grant | Custom flow for user-scoped microapp tokens |
| JWKS Publishing | Serves public keys in standard JWKS format |
What This Service Does NOT Do
| Function | Where It's Handled |
|---|---|
| User Authentication | External IDP (Ex:Asgardeo) |
| User Identity Management | External IDP (Ex:Asgardeo) |
| Token Validation | superapp-backend / Microapp backends |
| Session Management | Not applicable (stateless tokens) |
Features
Core Features
- OAuth2 Client Credentials Grant - Standard OAuth2 flow for services
- Custom User Context Grant - Tokens with embedded user identity
- RS256 JWT Signing - Industry-standard asymmetric signing
- JWKS Publishing - Standard endpoint for public key distribution
- Multi-Key Support - Load and manage multiple signing keys
- Zero-Downtime Key Rotation - Rotate keys without service restart
Security Features
- Hashed Client Secrets - Secrets stored as SHA256 hashes
- Request Body Limits - Protection against large payload attacks
- Structured Logging - JSON logs with
slogfor audit trails - Key ID (kid) in JWT Header - Enables key identification for validation
Getting Started
Prerequisites
- Go 1.21 or later
- MySQL 8.0+ (or compatible)
- OpenSSL (for key generation)
Installation
# Clone the repository
git clone <repository-url>
cd superapp-mobile/token-issure-service
# Install dependencies
go mod download
# Generate development keys (first time only)
./scripts/generate-keys.sh "dev-key-example" "./keys/dev"
Configuration
Copy the example environment file and configure:
Environment Variables
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 8081 |
DB_USER |
Database username | root |
DB_PASSWORD |
Database password | password |
DB_HOST |
Database host | 127.0.0.1 |
DB_PORT |
Database port | 3306 |
DB_NAME |
Database name | superapp |
TOKEN_EXPIRY_SECONDS |
Token validity period | 3600 |
Key Configuration (Choose One)
Option 1: Single Key Mode (Simple, backward compatible)
PRIVATE_KEY_PATH=./keys/dev/dev-key-example_private.pem
PUBLIC_KEY_PATH=./keys/dev/dev-key-example_public.pem
JWKS_PATH=./keys/dev/dev-key-example_jwks.json
Option 2: Directory Mode (Recommended for production)
Running the Service
# Development
go run cmd/server/main.go
# Build and run
go build -o bin/token-issuer cmd/server/main.go
./bin/token-issuer
The service will start on http://localhost:8081 (or configured port).
API Reference
Hot Key Reload (Zero-Downtime Rotation)
You can rotate keys without restarting the service by using the reload endpoint. This is useful for production environments where zero downtime is required.
- Generate new keys in the keys directory (e.g.,
key-2_private.pem,key-2_public.pem). - Trigger reload:
- Verify: The service will load the new keys and add them to the JWKS.
- Update Active Key: Set the new key as active to start signing tokens with it: Both keys remain valid for verification, allowing seamless rotation without invalidating existing tokens.
Note: The
admin/reload-keysendpoint re-scans the directory specified byKEYS_DIR. Ensure the new key files are present before calling it.
1. OAuth Token Endpoint
Issues tokens for service-to-service authentication using OAuth2 Client Credentials grant.
Endpoint: POST /oauth/token
Content Type: application/x-www-form-urlencoded
Request (Recommended - Basic Auth Header)
curl -X POST http://localhost:8081/oauth/token \
-u "microapp-news:your-secret-here" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"
This uses HTTP Basic Authentication where:
client_id→ Basic Auth usernameclient_secret→ Basic Auth password
The -u flag in curl automatically encodes credentials as Authorization: Basic base64(client_id:client_secret)
Alternative: Form Data (Not Recommended)
curl -X POST http://localhost:8081/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=microapp-news" \
-d "client_secret=your-secret-here"
⚠️ Sending secrets in form body is less secure than Basic Auth header
Response (Success - 200)
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRldi1rZXktZXhhbXBsZSIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Response (Error - 400/401)
Error Codes
| Code | Description |
|---|---|
invalid_request |
Malformed request |
invalid_client |
Client not found or wrong credentials |
unsupported_grant_type |
Grant type not supported |
server_error |
Internal server error |
2. Create OAuth Client Endpoint
Creates a new OAuth2 client with a securely generated client secret. This endpoint is used to register new microapp backends that need to authenticate with the SuperApp platform.
Endpoint: POST /oauth/clients
Content Type: application/json
Request
curl -X POST http://localhost:8081/oauth/clients \
-H "Content-Type: application/json" \
-d '{
"client_id": "microapp-weather",
"name": "Weather Microapp Backend",
"scopes": "read write notifications:send"
}'
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id |
string | Yes | Unique identifier for the OAuth client (also serves as microapp ID) |
name |
string | Yes | Human-readable name for the client |
scopes |
string | No | Comma-separated list of scopes (e.g., "read write admin") |
Response (Success - 201 Created)
{
"client_id": "microapp-weather",
"client_secret": "aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW",
"name": "Weather Microapp Backend",
"scopes": "read write notifications:send",
"is_active": true
}
⚠️ Important: The
client_secretis only returned once during client creation. Store it securely as it cannot be retrieved later.
Response (Error - 400 Bad Request)
Response (Error - 409 Conflict)
Security Features
- Secure Generation: Client secrets are generated using cryptographically secure random number generation (
crypto/rand) - 32-Character Length: Secrets contain 32 alphanumeric characters (a-z, A-Z, 0-9)
- Hashed Storage: Secrets are hashed using SHA-256 before being stored in the database
- One-Time Visibility: The plain text secret is only returned once and cannot be retrieved later
3. User Context Token Endpoint
Generates tokens with embedded user identity for microapp frontends. This endpoint is called by go-backend during token exchange.
Endpoint: POST /oauth/token/user
Content Type: application/x-www-form-urlencoded
Request
curl -X POST http://localhost:8081/oauth/token/user \
-d "grant_type=user_context" \
-d "user_email=user@example.com" \
-d "microapp_id=microapp-news" \
-d "scope=read write"
Response (Success - 200)
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRldi1rZXktZXhhbXBsZSIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Token Claims
The generated token includes:
{
"iss": "superapp-idp",
"sub": "microapp-news",
"aud": ["superapp-api"],
"exp": 1701648000,
"iat": 1701644400,
"nbf": 1701644400,
"email": "user@example.com",
"scope": "read write"
}
4. JWKS Endpoint
Serves the JSON Web Key Set containing public keys for token validation.
Endpoint: GET /.well-known/jwks.json
Response (200)
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "dev-key-example",
"n": "xOw3Yt...",
"e": "AQAB",
"alg": "RS256"
},
{
"kty": "RSA",
"use": "sig",
"kid": "dev-key-2",
"n": "yPx4Zu...",
"e": "AQAB",
"alg": "RS256"
}
]
}
Usage in Token Validation
Microapp backends should:
- Fetch JWKS from
go-backend/.well-known/jwks.json(proxied) - Cache the JWKS with periodic refresh (recommended: 1 hour)
- Use the
kidheader in incoming JWTs to select the correct key - Validate the token signature using the matching public key
Token Structure
JWT Header
Service Token Claims (Client Credentials)
{
"iss": "superapp-idp",
"sub": "microapp-news",
"aud": ["superapp-api"],
"exp": 1701648000,
"iat": 1701644400,
"nbf": 1701644400,
"scope": "notifications:send users:read"
}
User Context Token Claims
{
"iss": "superapp-idp",
"sub": "user@example.com",
"aud": ["microapp-news"],
"exp": 1701648000,
"iat": 1701644400,
"nbf": 1701644400,
"microapp_id": "microapp-news",
"scope": "read write"
}
Claim Descriptions
Service Token (Client Credentials)
| Claim | Description |
|---|---|
iss |
Issuer - always superapp-idp |
sub |
Subject - the microapp/client ID |
aud |
Audience - superapp-api |
exp |
Expiration time (Unix timestamp) |
iat |
Issued at (Unix timestamp) |
nbf |
Not valid before (Unix timestamp) |
scope |
Space-separated list of scopes |
User Context Token
| Claim | Description |
|---|---|
iss |
Issuer - always superapp-idp |
sub |
Subject - the user's email |
aud |
Audience - the target microapp ID |
exp |
Expiration time (Unix timestamp) |
iat |
Issued at (Unix timestamp) |
nbf |
Not valid before (Unix timestamp) |
microapp_id |
The microapp this token is valid for |
scope |
Space-separated list of scopes |
Key Management
Single Key Mode
For simple deployments or development:
# Directory structure
keys/
└── dev/
├── dev-key-example_private.pem # Keep secure!
├── dev-key-example_public.pem
└── dev-key-example_jwks.json
# Configuration
PRIVATE_KEY_PATH=./keys/dev/dev-key-example_private.pem
PUBLIC_KEY_PATH=./keys/dev/dev-key-example_public.pem
JWKS_PATH=./keys/dev/dev-key-example_jwks.json
Directory Mode (Multi-Key)
For production with key rotation support:
# Directory structure
keys/
└── prod/
├── prod-key-2024-q1_private.pem
├── prod-key-2024-q1_public.pem
├── prod-key-2024-q2_private.pem # New key for rotation
└── prod-key-2024-q2_public.pem
# Configuration
KEYS_DIR=./keys/prod
ACTIVE_KEY_ID=prod-key-2024-q1 # Currently signing key
How it works:
- All keys in the directory are loaded on startup
- JWKS contains all public keys (enables validation of any key)
- Only the
ACTIVE_KEY_IDis used for signing new tokens - Old tokens remain valid until they expire
Key Rotation
See https://github.com/LSFLK/superapp-mobile/blob/main/backend-services/token-service/docs/KEY_ROTATION.md for detailed instructions.
Quick Overview:
# 1. Generate new key
./scripts/generate-keys.sh "prod-key-2024-q2" "./keys/prod" 4096
# 2. Reload keys (no restart needed)
curl -X POST http://localhost:8081/admin/reload-keys
# 3. Set new key as active
curl -X POST "http://localhost:8081/admin/active-key?key_id=prod-key-2024-q2"
# 4. Wait for old tokens to expire, then remove old key
Security Considerations
Production Checklist
- Never commit private keys - Add
*.pemto.gitignore - Use 4096-bit keys - More secure than 2048-bit
- Store secrets securely - Use AWS Secrets Manager, Vault, etc.
- Rotate keys regularly - Every 90 days recommended
- Use different keys per environment - dev/staging/prod
- Enable TLS - Run behind HTTPS proxy in production
- Limit token expiry - Default 1 hour, adjust as needed
- Monitor token issuance - Set up alerts for anomalies
Client Secret Storage
Client secrets are stored as SHA256 hashes: