mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-01 15:34:25 +00:00
* Introduce OAuth2 authentication examples for Google, GitHub, and custom providers. * Implement OAuth2 methods for handling authentication, token refresh, and logout. * Create a flexible structure for supporting multiple OAuth2 providers. * Enhance DatabaseAuthenticator to manage OAuth2 sessions and user creation. * Add database schema setup for OAuth2 user and session management.
7.6 KiB
7.6 KiB
OAuth2 Refresh Token - Quick Reference
Quick Setup (3 Steps)
1. Initialize Authenticator
auth := security.NewGoogleAuthenticator(
"client-id",
"client-secret",
"http://localhost:8080/auth/google/callback",
db,
)
2. OAuth2 Login Flow
// Login - Redirect to Google
router.HandleFunc("/auth/google/login", func(w http.ResponseWriter, r *http.Request) {
state, _ := auth.OAuth2GenerateState()
authURL, _ := auth.OAuth2GetAuthURL("google", state)
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
})
// Callback - Store tokens
router.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
loginResp, _ := auth.OAuth2HandleCallback(
r.Context(),
"google",
r.URL.Query().Get("code"),
r.URL.Query().Get("state"),
)
// Save refresh_token on client
// loginResp.RefreshToken - Store this securely!
// loginResp.Token - Session token for API calls
})
3. Refresh Endpoint
router.HandleFunc("/auth/refresh", func(w http.ResponseWriter, r *http.Request) {
var req struct {
RefreshToken string `json:"refresh_token"`
}
json.NewDecoder(r.Body).Decode(&req)
// Refresh token
loginResp, err := auth.OAuth2RefreshToken(r.Context(), req.RefreshToken, "google")
if err != nil {
http.Error(w, err.Error(), 401)
return
}
json.NewEncoder(w).Encode(loginResp)
})
Multi-Provider Example
// Configure multiple providers
auth := security.NewDatabaseAuthenticator(db).
WithOAuth2(security.OAuth2Config{
ProviderName: "google",
ClientID: "google-client-id",
ClientSecret: "google-secret",
RedirectURL: "http://localhost:8080/auth/google/callback",
Scopes: []string{"openid", "profile", "email"},
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://oauth2.googleapis.com/token",
UserInfoURL: "https://www.googleapis.com/oauth2/v2/userinfo",
}).
WithOAuth2(security.OAuth2Config{
ProviderName: "github",
ClientID: "github-client-id",
ClientSecret: "github-secret",
RedirectURL: "http://localhost:8080/auth/github/callback",
Scopes: []string{"user:email"},
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
UserInfoURL: "https://api.github.com/user",
})
// Refresh with provider selection
router.HandleFunc("/auth/refresh", func(w http.ResponseWriter, r *http.Request) {
var req struct {
RefreshToken string `json:"refresh_token"`
Provider string `json:"provider"` // "google" or "github"
}
json.NewDecoder(r.Body).Decode(&req)
loginResp, err := auth.OAuth2RefreshToken(r.Context(), req.RefreshToken, req.Provider)
if err != nil {
http.Error(w, err.Error(), 401)
return
}
json.NewEncoder(w).Encode(loginResp)
})
Client-Side JavaScript
// Automatic token refresh on 401
async function apiCall(url) {
let response = await fetch(url, {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
}
});
// Token expired - refresh it
if (response.status === 401) {
await refreshToken();
// Retry request with new token
response = await fetch(url, {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
}
});
}
return response.json();
}
async function refreshToken() {
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
refresh_token: localStorage.getItem('refresh_token'),
provider: localStorage.getItem('provider')
})
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('access_token', data.token);
localStorage.setItem('refresh_token', data.refresh_token);
} else {
// Refresh failed - redirect to login
window.location.href = '/login';
}
}
API Methods
| Method | Parameters | Returns |
|---|---|---|
OAuth2RefreshToken |
ctx, refreshToken, provider |
*LoginResponse, error |
OAuth2HandleCallback |
ctx, provider, code, state |
*LoginResponse, error |
OAuth2GetAuthURL |
provider, state |
string, error |
OAuth2GenerateState |
none | string, error |
OAuth2GetProviders |
none | []string |
LoginResponse Structure
type LoginResponse struct {
Token string // New session token for API calls
RefreshToken string // Refresh token (store securely)
User *UserContext // User information
ExpiresIn int64 // Seconds until token expires
}
Database Stored Procedures
resolvespec_oauth_getrefreshtoken(refresh_token)- Get session by refresh tokenresolvespec_oauth_updaterefreshtoken(update_data)- Update tokens after refreshresolvespec_oauth_getuser(user_id)- Get user data
All procedures return: {p_success bool, p_error text, p_data jsonb}
Common Errors
| Error | Cause | Solution |
|---|---|---|
invalid or expired refresh token |
Token revoked/expired | Re-authenticate user |
OAuth2 provider 'xxx' not found |
Provider not configured | Add with WithOAuth2() |
failed to refresh token with provider |
Provider rejected request | Check credentials, re-auth user |
Security Checklist
- Use HTTPS for all OAuth2 endpoints
- Store refresh tokens securely (HttpOnly cookies or encrypted storage)
- Set cookie flags:
HttpOnly,Secure,SameSite=Strict - Implement rate limiting on refresh endpoint
- Log refresh attempts for audit
- Rotate tokens on refresh
- Revoke old sessions after successful refresh
Testing
# 1. Login and get refresh token
curl http://localhost:8080/auth/google/login
# Follow OAuth2 flow, get refresh_token from callback response
# 2. Refresh token
curl -X POST http://localhost:8080/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"ya29.xxx","provider":"google"}'
# 3. Use new token
curl http://localhost:8080/api/protected \
-H "Authorization: Bearer sess_abc123..."
Pre-configured Providers
// Google
auth := security.NewGoogleAuthenticator(clientID, secret, redirectURL, db)
// GitHub
auth := security.NewGitHubAuthenticator(clientID, secret, redirectURL, db)
// Microsoft
auth := security.NewMicrosoftAuthenticator(clientID, secret, redirectURL, db)
// Facebook
auth := security.NewFacebookAuthenticator(clientID, secret, redirectURL, db)
// All providers at once
auth := security.NewMultiProviderAuthenticator(db, map[string]security.OAuth2Config{
"google": {...},
"github": {...},
})
Provider-Specific Notes
- Add
access_type=offlineto get refresh token - Add
prompt=consentto force consent screen
authURL += "&access_type=offline&prompt=consent"
GitHub
- Refresh tokens not always provided
- May need to request
offline_accessscope
Microsoft
- Use
offline_accessscope for refresh token
- Tokens expire after 60 days by default
- Check app settings for token expiration policy
Complete Example
See /pkg/security/oauth2_examples.go line 250 for full working example.
For detailed documentation see /pkg/security/OAUTH2_REFRESH_TOKEN_IMPLEMENTATION.md.