Skip to content

Instantly share code, notes, and snippets.

@phith0n
Last active July 29, 2024 15:20
Show Gist options
  • Save phith0n/78ae5cdd4a9a9823cdeaa73cdbc7ab2f to your computer and use it in GitHub Desktop.
Save phith0n/78ae5cdd4a9a9823cdeaa73cdbc7ab2f to your computer and use it in GitHub Desktop.
Golang go-oidc example
package web
import (
"context"
"errors"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/hashicorp/golang-lru/v2/expirable"
"golang.org/x/oauth2"
"time"
)
func NewOIDCClient(redirectURL, clientID, clientSecret string) (*OIDCClient, error) {
provider, err := oidc.NewProvider(context.Background(), "https://accounts.google.com")
if err != nil {
return nil, err
}
var client = &OIDCClient{
oauth2Config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
},
storage: expirable.NewLRU[string, *stateObj](500, nil, 10*time.Minute),
oidcProvider: provider,
verifier: provider.Verifier(&oidc.Config{ClientID: clientID, SupportedSigningAlgs: []string{"RS256"}}),
}
return client, nil
}
type stateObj struct {
State string
Nonce string
CodeVerifier string
}
type OIDCClient struct {
oidcProvider *oidc.Provider
verifier *oidc.IDTokenVerifier
oauth2Config *oauth2.Config
storage *expirable.LRU[string, *stateObj]
}
// AuthCodeURL
// State is to defense CSRF attack, Nonce is to ensure the process can be done only one time,
// CodeVerifier is for PKCE verification
func (gc *OIDCClient) AuthCodeURL() string {
state := &stateObj{
State: oauth2.GenerateVerifier(),
Nonce: oauth2.GenerateVerifier(),
CodeVerifier: oauth2.GenerateVerifier(),
}
gc.storage.Add(state.State, state)
return gc.oauth2Config.AuthCodeURL(state.State, oidc.Nonce(state.Nonce), oauth2.S256ChallengeOption(state.CodeVerifier))
}
func (gc *OIDCClient) Verify(ctx context.Context, code, rawState string) (*oidc.IDToken, error) {
state, ok := gc.storage.Peek(rawState)
if !ok {
return nil, errors.New("invalid state")
}
gc.storage.Remove(rawState)
token, err := gc.oauth2Config.Exchange(ctx, code, oauth2.VerifierOption(state.CodeVerifier))
if err != nil {
return nil, errors.New("invalid code")
}
rawIdToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, errors.New("id_token not found")
}
idToken, err := gc.verifier.Verify(ctx, rawIdToken)
if err != nil {
return nil, errors.New("invalid id_token")
}
if idToken.Nonce != state.Nonce {
return nil, errors.New("invalid nonce")
}
return idToken, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment