Go (go-oidc)
See the Applications overview for prerequisites, configuration endpoints, and available scopes.
go-oidc and the standard oauth2 package provide OIDC support for Go applications.
Install the dependencies:
go get github.com/coreos/go-oidc/v3/oidc
go get golang.org/x/oauth2
Provider discovery
Use OIDC discovery to configure the provider automatically:
import (
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
provider, err := oidc.NewProvider(ctx, "https://us.vouch.sh")
if err != nil {
log.Fatalf("Failed to create OIDC provider: %v", err)
}
verifier := provider.Verifier(&oidc.Config{ClientID: clientID})
oauth2Config := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: "http://localhost:3000/callback",
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "email"},
}
Login handler
Generate a PKCE verifier and redirect to the authorization endpoint:
func handleLogin(w http.ResponseWriter, r *http.Request) {
state := generateRandomState()
pkceVerifier := oauth2.GenerateVerifier()
// Store state and verifier in session (in-memory map shown for brevity)
states[state] = true
pkceVerifiers[state] = pkceVerifier
http.Redirect(w, r, oauth2Config.AuthCodeURL(
state,
oauth2.S256ChallengeOption(pkceVerifier),
), http.StatusFound)
}
Callback handler
Exchange the authorization code using the PKCE verifier, then verify and extract claims from the ID token:
func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.URL.Query().Get("state")
if !states[state] {
http.Error(w, "Invalid state", http.StatusBadRequest)
return
}
delete(states, state)
pkceVerifier := pkceVerifiers[state]
delete(pkceVerifiers, state)
code := r.URL.Query().Get("code")
token, err := oauth2Config.Exchange(
r.Context(), code, oauth2.VerifierOption(pkceVerifier),
)
if err != nil {
http.Error(w, "Token exchange failed", http.StatusInternalServerError)
return
}
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
http.Error(w, "No ID token", http.StatusInternalServerError)
return
}
idToken, err := verifier.Verify(r.Context(), rawIDToken)
if err != nil {
http.Error(w, "Token verification failed", http.StatusInternalServerError)
return
}
var claims struct {
Email string `json:"email"`
HardwareVerified bool `json:"hardware_verified"`
}
if err := idToken.Claims(&claims); err != nil {
http.Error(w, "Failed to parse claims", http.StatusInternalServerError)
return
}
// claims.Email and claims.HardwareVerified are now available
}
Rich Authorization Requests
To request structured permissions beyond scopes, include authorization_details as a query parameter when building the authorization URL:
import "encoding/json"
authzDetails, _ := json.Marshal([]map[string]interface{}{
{"type": "account_access", "actions": []string{"read", "transfer"}},
})
url := oauth2Config.AuthCodeURL(
state,
oauth2.S256ChallengeOption(pkceVerifier),
oauth2.SetAuthURLParam("authorization_details", string(authzDetails)),
)
http.Redirect(w, r, url, http.StatusFound)
See the Rich Authorization Requests section for the full authorization_details format.