This guide provides real-world examples of how to use AuthGate for different scenarios.
- Use Cases and Examples
A backend microservice, daemon, or CI/CD job needs to call a protected internal API without any user involvement.
- No browser or user interaction
- Service authenticates itself using a pre-shared client secret
- Token cached in memory and refreshed automatically when expired
- No refresh token — simplifies secret rotation (just update the secret)
1. Create a confidential OAuth client in Admin:
Admin → OAuth Clients → New Client
Client Name: "Metrics Exporter"
Client Type: Confidential
Grant Types: [x] Client Credentials Flow (RFC 6749 §4.4)
Scopes: metrics:read
2. Save the generated client_id and client_secret.
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
const (
authServer = "https://auth.example.com"
clientID = "your-client-id"
clientSecret = "your-client-secret"
scope = "metrics:read"
)
type tokenCache struct {
mu sync.Mutex
token string
expiresAt time.Time
}
var cache = &tokenCache{}
func (c *tokenCache) getToken() (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
// Return cached token if still valid (with 30s buffer)
if c.token != "" && time.Now().Add(30*time.Second).Before(c.expiresAt) {
return c.token, nil
}
// Request new token via Client Credentials Grant
data := url.Values{
"grant_type": {"client_credentials"},
"scope": {scope},
}
req, _ := http.NewRequest("POST", authServer+"/oauth/token",
strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(clientID, clientSecret) // Preferred per RFC 6749 §2.3.1
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("token request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("token request returned %d", resp.StatusCode)
}
var result struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("failed to decode token response: %w", err)
}
c.token = result.AccessToken
c.expiresAt = time.Now().Add(time.Duration(result.ExpiresIn) * time.Second)
return c.token, nil
}
func callProtectedAPI() error {
token, err := cache.getToken()
if err != nil {
return err
}
req, _ := http.NewRequest("GET", "https://api.example.com/metrics", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Printf("API response status: %s\n", resp.Status)
return nil
}
func main() {
if err := callProtectedAPI(); err != nil {
fmt.Printf("Error: %v\n", err)
}
}# Get access token
TOKEN=$(curl -s -X POST https://auth.example.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=metrics:read" \
| jq -r '.access_token')
# Use the token
curl -H "Authorization: Bearer $TOKEN" \
https://api.example.com/metricsimport requests
import time
import threading
class M2MTokenClient:
def __init__(self, auth_server, client_id, client_secret, scope=""):
self.auth_server = auth_server
self.client_id = client_id
self.client_secret = client_secret
self.scope = scope
self._token = None
self._expires_at = 0
self._lock = threading.Lock()
def get_token(self):
with self._lock:
# Return cached token if still valid (30s buffer)
if self._token and time.time() + 30 < self._expires_at:
return self._token
response = requests.post(
f"{self.auth_server}/oauth/token",
data={
"grant_type": "client_credentials",
"scope": self.scope,
},
auth=(self.client_id, self.client_secret), # HTTP Basic Auth
)
response.raise_for_status()
data = response.json()
self._token = data["access_token"]
self._expires_at = time.time() + data["expires_in"]
return self._token
def get_headers(self):
return {"Authorization": f"Bearer {self.get_token()}"}
# Usage
client = M2MTokenClient(
auth_server="https://auth.example.com",
client_id="your-client-id",
client_secret="your-client-secret",
scope="metrics:read",
)
response = requests.get(
"https://api.example.com/metrics",
headers=client.get_headers(),
)
print(response.json())- Store
client_secretin environment variables or a secrets manager (never in source code) - Keep
CLIENT_CREDENTIALS_TOKEN_EXPIRATIONshort (default: 1h); no rotation mechanism exists since there is no refresh token - Rotate the client secret periodically via Admin → OAuth Clients → Regenerate Secret
- Restrict scopes to the minimum required by the service
- Audit token issuance via Admin → Audit Logs (event:
CLIENT_CREDENTIALS_TOKEN_ISSUED)
You're building a CLI tool (like gh or aws-cli) that needs to access user resources on your platform.
- Users should authenticate via browser (not enter password in CLI)
- CLI should receive an access token after authorization
- Token should be stored securely for future use
- Automatic token refresh when expired
1. Server Setup:
Deploy AuthGate with your OAuth client:
# .env
BASE_URL=https://auth.yourplatform.com
JWT_SECRET=your-strong-secret
SESSION_SECRET=your-session-secret2. CLI Implementation:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
)
type TokenStorage struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresAt time.Time `json:"expires_at"`
}
const (
clientID = "your-client-id"
tokenFile = ".myapp-tokens.json"
authServer = "https://auth.yourplatform.com"
)
func main() {
// Try to load existing tokens
tokens, err := loadTokens()
if err != nil || tokens.AccessToken == "" {
// First time: perform device flow
tokens, err = performDeviceFlow()
if err != nil {
fmt.Println("Authentication failed:", err)
os.Exit(1)
}
saveTokens(tokens)
}
// Check if token is expired
if time.Now().After(tokens.ExpiresAt) {
fmt.Println("Token expired, refreshing...")
tokens, err = refreshToken(tokens.RefreshToken)
if err != nil {
fmt.Println("Token refresh failed, re-authenticating...")
tokens, err = performDeviceFlow()
if err != nil {
fmt.Println("Authentication failed:", err)
os.Exit(1)
}
}
saveTokens(tokens)
}
// Use the access token
makeAPICall(tokens.AccessToken)
}
func performDeviceFlow() (*TokenStorage, error) {
// Step 1: Request device code
resp, err := http.PostForm(authServer+"/oauth/device/code",
url.Values{"client_id": {clientID}})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var deviceCodeResp struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
Interval int `json:"interval"`
ExpiresIn int `json:"expires_in"`
}
json.NewDecoder(resp.Body).Decode(&deviceCodeResp)
// Step 2: Display instructions to user
fmt.Printf("\n🔐 Authentication Required\n\n")
fmt.Printf("Visit: %s\n", deviceCodeResp.VerificationURI)
fmt.Printf("Enter code: %s\n\n", deviceCodeResp.UserCode)
fmt.Println("Waiting for authorization...")
// Step 3: Poll for token
ticker := time.NewTicker(time.Duration(deviceCodeResp.Interval) * time.Second)
defer ticker.Stop()
timeout := time.After(time.Duration(deviceCodeResp.ExpiresIn) * time.Second)
for {
select {
case <-ticker.C:
resp, err := http.PostForm(authServer+"/oauth/token", url.Values{
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
"device_code": {deviceCodeResp.DeviceCode},
"client_id": {clientID},
})
if err != nil {
continue
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var tokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
Error string `json:"error"`
}
json.Unmarshal(body, &tokenResp)
if tokenResp.Error == "authorization_pending" {
continue
}
if tokenResp.AccessToken != "" {
fmt.Println("✅ Authentication successful!")
return &TokenStorage{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
ExpiresAt: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
}, nil
}
return nil, fmt.Errorf("authentication failed: %s", tokenResp.Error)
case <-timeout:
return nil, fmt.Errorf("authentication timeout")
}
}
}
func refreshToken(refreshToken string) (*TokenStorage, error) {
resp, err := http.PostForm(authServer+"/oauth/token", url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {refreshToken},
"client_id": {clientID},
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
}
json.NewDecoder(resp.Body).Decode(&tokenResp)
return &TokenStorage{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
ExpiresAt: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
}, nil
}
func loadTokens() (*TokenStorage, error) {
data, err := ioutil.ReadFile(tokenFile)
if err != nil {
return nil, err
}
var tokens TokenStorage
err = json.Unmarshal(data, &tokens)
return &tokens, err
}
func saveTokens(tokens *TokenStorage) error {
data, err := json.Marshal(tokens)
if err != nil {
return err
}
return ioutil.WriteFile(tokenFile, data, 0600) // Secure permissions
}
func makeAPICall(accessToken string) {
req, _ := http.NewRequest("GET", "https://api.yourplatform.com/user/profile", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("API call failed:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized {
fmt.Println("Token invalid, need to re-authenticate")
os.Remove(tokenFile)
os.Exit(1)
}
// Process response...
fmt.Println("API call successful!")
}3. User Experience:
$ myapp-cli deploy
🔐 Authentication Required
Visit: https://auth.yourplatform.com/device
Enter code: ABCD-1234
Waiting for authorization...
✅ Authentication successful!
Deploying application...You're building a smart home device (security camera, thermostat) that needs user authorization to access cloud services.
- Device has no keyboard or browser
- Device displays code on screen/LCD
- User authorizes via mobile app or browser
- Token stored in device memory
- Automatic token refresh
1. Device Code (Python):
import requests
import time
import json
from pathlib import Path
CLIENT_ID = "iot-device-client-id"
AUTH_SERVER = "https://auth.yourplatform.com"
TOKEN_FILE = "/etc/mydevice/token.json"
def device_login():
# Request device code
response = requests.post(f"{AUTH_SERVER}/oauth/device/code", data={
"client_id": CLIENT_ID
})
data = response.json()
device_code = data["device_code"]
user_code = data["user_code"]
verification_uri = data["verification_uri"]
interval = data["interval"]
expires_in = data["expires_in"]
# Display on device screen
print(f"Go to: {verification_uri}")
print(f"Code: {user_code}")
# Or show QR code
qr_url = f"{verification_uri}?user_code={user_code}"
display_qr_code(qr_url)
# Poll for token
start_time = time.time()
while time.time() - start_time < expires_in:
time.sleep(interval)
response = requests.post(f"{AUTH_SERVER}/oauth/token", data={
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code,
"client_id": CLIENT_ID
})
token_data = response.json()
if "error" in token_data:
if token_data["error"] == "authorization_pending":
continue
else:
raise Exception(f"Authorization failed: {token_data['error']}")
# Save tokens securely
save_tokens(token_data)
return token_data
raise Exception("Authorization timeout")
def save_tokens(token_data):
with open(TOKEN_FILE, 'w') as f:
json.dump(token_data, f)
# Set secure permissions
Path(TOKEN_FILE).chmod(0o600)
def load_tokens():
try:
with open(TOKEN_FILE, 'r') as f:
return json.load(f)
except FileNotFoundError:
return None
def refresh_token(refresh_token):
response = requests.post(f"{AUTH_SERVER}/oauth/token", data={
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": CLIENT_ID
})
return response.json()
def display_qr_code(url):
# Generate and display QR code on device screen
import qrcode
qr = qrcode.QRCode()
qr.add_data(url)
qr.make()
# Display on device LCD/OLED
# ...
def main():
tokens = load_tokens()
if tokens is None:
print("First time setup, authenticating...")
tokens = device_login()
# Check if token expired
if is_token_expired(tokens):
print("Refreshing token...")
tokens = refresh_token(tokens["refresh_token"])
save_tokens(tokens)
# Use access token to access cloud services
access_token = tokens["access_token"]
# ... device operations
if __name__ == "__main__":
main()2. User Experience:
Device LCD shows:
╔═══════════════════╗
║ Setup Required ║
║ ║
║ Visit: ║
║ auth.app.com ║
║ ║
║ Code: WXYZ-5678 ║
║ ║
║ [QR Code] ║
╚═══════════════════╝
User scans QR code or visits URL on phone, enters code, device activates.
Your CI/CD pipeline needs to deploy applications to your platform on behalf of developers.
- Pipeline runs in headless environment
- Authenticate on behalf of repository owner
- Use long-lived refresh tokens
- Revoke access when developer leaves team
1. One-Time Setup (Developer's Machine):
#!/bin/bash
# setup-ci-token.sh
CLIENT_ID="cicd-client-id"
AUTH_SERVER="https://auth.yourplatform.com"
# Perform device flow
response=$(curl -s -X POST "$AUTH_SERVER/oauth/device/code" \
-d "client_id=$CLIENT_ID")
device_code=$(echo $response | jq -r '.device_code')
user_code=$(echo $response | jq -r '.user_code')
verification_uri=$(echo $response | jq -r '.verification_uri')
interval=$(echo $response | jq -r '.interval')
echo "Visit: $verification_uri"
echo "Enter code: $user_code"
echo ""
echo "Waiting for authorization..."
# Poll for token
while true; do
sleep $interval
token_response=$(curl -s -X POST "$AUTH_SERVER/oauth/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "device_code=$device_code" \
-d "client_id=$CLIENT_ID")
error=$(echo $token_response | jq -r '.error // empty')
if [ "$error" = "authorization_pending" ]; then
continue
elif [ -z "$error" ]; then
# Success
refresh_token=$(echo $token_response | jq -r '.refresh_token')
echo ""
echo "✅ Authentication successful!"
echo ""
echo "Add this secret to your CI/CD environment:"
echo "PLATFORM_REFRESH_TOKEN=$refresh_token"
break
else
echo "Error: $error"
exit 1
fi
done2. CI/CD Pipeline (.github/workflows/deploy.yml):
name: Deploy to Platform
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get Access Token
id: auth
run: |
response=$(curl -s -X POST https://auth.yourplatform.com/oauth/token \
-d "grant_type=refresh_token" \
-d "refresh_token=${{ secrets.PLATFORM_REFRESH_TOKEN }}" \
-d "client_id=cicd-client-id")
access_token=$(echo $response | jq -r '.access_token')
echo "::add-mask::$access_token"
echo "access_token=$access_token" >> $GITHUB_OUTPUT
- name: Deploy Application
run: |
curl -X POST https://api.yourplatform.com/deploy \
-H "Authorization: Bearer ${{ steps.auth.outputs.access_token }}" \
-H "Content-Type: application/json" \
-d '{"repository":"${{ github.repository }}"}'3. Token Management:
When developer leaves team, revoke their CI/CD token via web UI:
# Admin logs in and revokes token
curl -X POST https://auth.yourplatform.com/account/sessions/revoke-all \
-H "Cookie: admin-session=..."Building a streaming app for Smart TVs where users need to login to access their account.
- Display code on TV screen
- User enters code on mobile/web
- Seamless experience across devices
- Remember device for 30 days
TV App (React Native for TV):
import React, { useState, useEffect } from "react";
const AUTH_SERVER = "https://auth.streamingapp.com";
const CLIENT_ID = "smart-tv-client";
function LoginScreen() {
const [userCode, setUserCode] = useState("");
const [verificationUri, setVerificationUri] = useState("");
const [status, setStatus] = useState("loading");
useEffect(() => {
initiateDeviceFlow();
}, []);
async function initiateDeviceFlow() {
// Request device code
const response = await fetch(`${AUTH_SERVER}/oauth/device/code`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ client_id: CLIENT_ID }),
});
const data = await response.json();
setUserCode(data.user_code);
setVerificationUri(data.verification_uri);
setStatus("waiting");
// Start polling
pollForToken(data.device_code, data.interval);
}
async function pollForToken(deviceCode, interval) {
const pollInterval = setInterval(async () => {
const response = await fetch(`${AUTH_SERVER}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
device_code: deviceCode,
client_id: CLIENT_ID,
}),
});
const data = await response.json();
if (data.error === "authorization_pending") {
return; // Keep polling
}
clearInterval(pollInterval);
if (data.access_token) {
// Save tokens securely
await saveTokens(data);
setStatus("success");
// Navigate to home screen
} else {
setStatus("error");
}
}, interval * 1000);
}
return (
<View style={styles.container}>
{status === "waiting" && (
<>
<Text style={styles.title}>Sign In</Text>
<Text style={styles.instructions}>
On your phone or computer, visit:
</Text>
<Text style={styles.url}>{verificationUri}</Text>
<Text style={styles.instructions}>And enter this code:</Text>
<Text style={styles.code}>{userCode}</Text>
<QRCode value={`${verificationUri}?code=${userCode}`} />
<ActivityIndicator />
</>
)}
{status === "success" && (
<Text style={styles.success}>✅ Sign in successful!</Text>
)}
</View>
);
}A user suspects unauthorized access to their account and wants to revoke all device access.
1. User logs into web UI:
https://auth.yourplatform.com/login
2. Navigate to Sessions:
https://auth.yourplatform.com/account/sessions
3. Review Active Devices:
User sees list of authorized devices:
- iPhone (iOS App) - Authorized 2 days ago
- Chrome on Windows - Authorized 5 hours ago
⚠️ Suspicious - CLI Tool - Authorized 1 month ago
4. Revoke Specific Device:
Click "Revoke" next to suspicious device.
5. Or Revoke All Devices:
Click "Revoke All" button to sign out all devices immediately.
6. Re-authorize Legitimate Devices:
Legitimate devices will prompt for re-authentication on next use.
# Admin can also revoke all tokens for a user
sqlite3 oauth.db "UPDATE access_tokens SET status='revoked' WHERE user_id='user-uuid';"
# Check audit logs for suspicious activity
curl "https://auth.yourplatform.com/admin/audit/api?user_id=user-uuid&since=7d" \
-H "Cookie: admin-session=..."A user has multiple devices (phone, laptop, tablet, TV) and wants to manage access from a central location.
1. View All Active Sessions:
Web UI at /account/sessions shows:
| Device | Client | Authorized | Last Used | Status | Actions |
|---|---|---|---|---|---|
| iPhone 12 | iOS App | 2 days ago | 2 min ago | Active | Revoke |
| MacBook Pro | Desktop App | 1 week ago | 1 hour ago | Active | Revoke |
| iPad | iOS App | 3 days ago | Never | Active | Revoke |
| Smart TV | TV App | 1 month ago | 2 days ago | Active | Revoke |
| CLI Tool | Developer Tools | 2 weeks ago | 5 min ago | Active | Revoke |
2. Selective Revocation:
User can revoke specific device without affecting others (thanks to fixed refresh token mode).
3. Bulk Actions:
- "Revoke All" - Sign out all devices (security incident)
- "Revoke Inactive" - Remove devices not used in 30+ days
4. Device Identification:
Show helpful information:
- Client name ("iOS App", "Desktop App")
- Authorization timestamp
- Last used timestamp
- IP address (privacy-sensitive)
- User-agent (if available)
Next Steps:
- Client Credentials Flow Guide - M2M / service-to-service authentication
- Authorization Code Flow Guide - Web and mobile app integration
- Development Guide - Build your own integration
- API Reference (README) - Endpoint documentation
- Device Code CLI - Working reference implementation
- Hybrid CLI - Browser flow with auto-fallback to Device Code