Documentation
¶
Overview ¶
Package federatedauth owns provider-mediated authentication orchestration — OAuth and SAML callback handlers plus account-linking helpers — that build on the account model in accounts/ and the session/middleware machinery in httpauth/.
What this package owns:
- SaveUserAndRedirect — entry point OAuth/SAML callbacks call after token exchange
- HandleLinkOAuthCallback — links a new provider channel to an existing user
- LinkOAuthConfig — config for the linking flow
- StartLinkOAuth / GetLinkingUserID — session bookkeeping for the linking flow
- AuthUserStore — composite store interface for callbacks
- EnsureAuthUserConfig / NewEnsureAuthUserFunc — provider-driven user creation
What this package deliberately does NOT own:
- Username/password flows — see localauth/
- The account data model (User/Identity/Channel) — see accounts/
- Session/cookie/CSRF machinery — see httpauth/
Index ¶
- type AuthUserStore
- type EnsureAuthUserConfig
- type EnsureAuthUserFunc
- type OAuthBridge
- func (b *OAuthBridge) GetLinkingUserID(r *http.Request) string
- func (b *OAuthBridge) HandleLinkOAuthCallback(linkingUserID, provider string, userInfo map[string]any, w http.ResponseWriter, ...)
- func (b *OAuthBridge) SaveUserAndRedirect(authtype, provider string, token *oauth2.Token, userInfo map[string]any, ...)
- func (b *OAuthBridge) StartLinkOAuth(r *http.Request, userID string)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthUserStore ¶
type AuthUserStore interface {
accounts.UserStore
accounts.IdentityStore
accounts.ChannelStore
// EnsureAuthUser orchestrates user creation/lookup across stores.
// This is the main entry point for OAuth and local authentication.
EnsureAuthUser(authtype string, provider string, token *oauth2.Token, userInfo map[string]any) (accounts.User, error)
}
AuthUserStore combines the store interfaces needed for federated-auth callbacks plus the EnsureAuthUser orchestration that turns provider userInfo into an accounts.User.
type EnsureAuthUserConfig ¶
type EnsureAuthUserConfig struct {
UserStore accounts.UserStore
IdentityStore accounts.IdentityStore
ChannelStore accounts.ChannelStore
UsernameStore accounts.UsernameStore // Optional — for username uniqueness
}
EnsureAuthUserConfig holds configuration for NewEnsureAuthUserFunc.
Example setup in your app:
config := federatedauth.EnsureAuthUserConfig{
UserStore: gaeStores.UserStore,
IdentityStore: gaeStores.IdentityStore,
ChannelStore: gaeStores.ChannelStore,
UsernameStore: gaeStores.UsernameStore, // optional
}
ensureUser := federatedauth.NewEnsureAuthUserFunc(config)
type EnsureAuthUserFunc ¶
type EnsureAuthUserFunc func(authtype string, provider string, token any, userInfo map[string]any) (accounts.User, error)
EnsureAuthUserFunc handles user creation/lookup for both OAuth and local authentication with channel linking support. Returned by NewEnsureAuthUserFunc; passed to the AuthUserStore implementations callbacks supply to SaveUserAndRedirect.
func NewEnsureAuthUserFunc ¶
func NewEnsureAuthUserFunc(config EnsureAuthUserConfig) EnsureAuthUserFunc
NewEnsureAuthUserFunc creates a function that handles user creation/lookup for both OAuth and local authentication with channel linking support.
Who Calls This ¶
This function is called by SaveUserAndRedirect after a successful OAuth callback or local login. The returned function implements the core logic for AuthUserStore.EnsureAuthUser.
Flow for OAuth (e.g., Google Login) ¶
- User clicks "Login with Google" → redirects to Google
- Google redirects back to /auth/google/callback with auth code
- OAuth handler exchanges code for token, fetches userInfo (email, name, picture)
- OAuth handler calls SaveUserAndRedirect(authtype="oauth", provider="google", token, userInfo)
- SaveUserAndRedirect calls UserStore.EnsureAuthUser → this function
- This function checks if email identity exists: - EXISTS: Link Google channel to existing user, update profile["channels"] - NEW: Create User, Identity (verified=true), Google Channel
- SaveUserAndRedirect creates JWT, sets cookies, redirects to app
Channel Linking Logic ¶
Multiple channels (local, google, github) can point to the same user via shared email:
User (id: abc123) ├── Identity: email → user@example.com ├── Channel: local → email:user@example.com (password_hash) ├── Channel: google → email:user@example.com (oauth profile) └── Channel: github → email:user@example.com (oauth profile)
User profile tracks linked providers: profile["channels"] = ["local", "google", "github"]
type OAuthBridge ¶
type OAuthBridge struct {
OneAuth *httpauth.OneAuth
UserStore AuthUserStore
}
OAuthBridge wires httpauth's session/cookie machinery to the federated-auth callback flow. Construct one per OneAuth instance and hand its methods to your OAuth provider handlers.
Example:
bridge := federatedauth.NewOAuthBridge(oneauth, authUserStore)
oneauth.AddAuth("/google", oa2.NewGoogleOAuth2(
clientID, clientSecret, callbackURL,
bridge.SaveUserAndRedirect,
).Handler())
func NewOAuthBridge ¶
func NewOAuthBridge(oneAuth *httpauth.OneAuth, userStore AuthUserStore) *OAuthBridge
NewOAuthBridge creates an OAuthBridge that callbacks dispatch through.
func (*OAuthBridge) GetLinkingUserID ¶
func (b *OAuthBridge) GetLinkingUserID(r *http.Request) string
GetLinkingUserID retrieves and clears the linking user ID from session. Call this in your OAuth callback to detect linking mode.
func (*OAuthBridge) HandleLinkOAuthCallback ¶
func (b *OAuthBridge) HandleLinkOAuthCallback(linkingUserID, provider string, userInfo map[string]any, w http.ResponseWriter, r *http.Request)
HandleLinkOAuthCallback links an OAuth provider channel onto an existing, already-logged-in user.
Who Calls This ¶
This is called by OAuth providers after the user authorizes linking. The flow is:
- Local-only user visits profile, clicks "Link Google Account"
- App stores user ID in session as "linkingUserID" (via b.StartLinkOAuth)
- App redirects to Google OAuth with special state
- Google redirects back to /auth/google/callback
- OAuth callback sees "linkingUserID" in session (via b.GetLinkingUserID)
- Instead of normal login, calls this handler to link the account
How to Set Up ¶
Modify your OAuth callback to detect linking mode:
func googleCallback(w http.ResponseWriter, r *http.Request) {
// ... exchange code for token, get userInfo ...
linkingUserID := bridge.GetLinkingUserID(r)
if linkingUserID != "" {
bridge.HandleLinkOAuthCallback(linkingUserID, "google", userInfo, w, r)
return
}
bridge.SaveUserAndRedirect("oauth", "google", token, userInfo, w, r)
}
What It Does ¶
- Verifies the OAuth email matches the user's existing email identity
- Creates OAuth channel for the provider
- Updates user profile["channels"] to include the new provider
- Redirects to callback URL
Security ¶
The OAuth email MUST match the user's existing email to prevent account hijacking. Users cannot link to a different email address.
func (*OAuthBridge) SaveUserAndRedirect ¶
func (b *OAuthBridge) SaveUserAndRedirect(authtype, provider string, token *oauth2.Token, userInfo map[string]any, w http.ResponseWriter, r *http.Request)
SaveUserAndRedirect is called by an OAuth callback handler with the auth token and user info after a successful auth flow and redirect.
Here is our opportunity to:
- Create a userId that is unique to our system based on userInfo
- Set the right session cookies from this
func (*OAuthBridge) StartLinkOAuth ¶
func (b *OAuthBridge) StartLinkOAuth(r *http.Request, userID string)
StartLinkOAuth initiates OAuth account linking by storing the user ID in session. Call this from your "Link [Provider] Account" button handler.
Example:
func handleLinkGoogle(w http.ResponseWriter, r *http.Request) {
userID := getLoggedInUserID(r)
bridge.StartLinkOAuth(r, userID)
http.Redirect(w, r, "/auth/google/", http.StatusFound)
}