10 "github.com/coreos/go-oidc/v3/oidc"
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
19// OIDCProvider represents an OIDC based Identity Provider
20type OIDCProvider struct {
26const oidcDefaultScope = "openid email profile"
28// NewOIDCProvider initiates a new OIDCProvider
29func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
30 name := "OpenID Connect"
32 if p.ProviderName != "" {
36 oidcProviderDefaults := providerDefaults{
42 scope: oidcDefaultScope,
45 if len(p.AllowedGroups) > 0 {
46 oidcProviderDefaults.scope += " groups"
49 p.setProviderDefaults(oidcProviderDefaults)
50 p.getAuthorizationHeaderFunc = makeOIDCHeader
54 SkipNonce: ptr.Deref(opts.InsecureSkipNonce, options.DefaultInsecureSkipNonce),
58var _ Provider = (*OIDCProvider)(nil)
60// GetLoginURL makes the LoginURL with optional nonce support
61func (p *OIDCProvider) GetLoginURL(redirectURI, state, nonce string, extraParams url.Values) string {
63 extraParams.Add("nonce", nonce)
65 // Response mode should only be set if a non default mode is requested
66 if p.AuthRequestResponseMode != "" {
67 extraParams.Add("response_mode", p.AuthRequestResponseMode)
70 loginURL := makeLoginURL(p.Data(), redirectURI, state, extraParams)
71 return loginURL.String()
74// Redeem exchanges the OAuth2 authentication token for an ID token
75func (p *OIDCProvider) Redeem(ctx context.Context, redirectURL, code, codeVerifier string) (*sessions.SessionState, error) {
76 clientSecret, err := p.GetClientSecret()
81 var opts []oauth2.AuthCodeOption
82 if codeVerifier != "" {
83 opts = append(opts, oauth2.SetAuthURLParam("code_verifier", codeVerifier))
88 ClientSecret: clientSecret,
89 Endpoint: oauth2.Endpoint{
90 TokenURL: p.RedeemURL.String(),
92 RedirectURL: redirectURL,
95 ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
96 token, err := c.Exchange(ctx, code, opts...)
98 return nil, fmt.Errorf("token exchange failed: %v", err)
101 return p.createSession(ctx, token, false)
104// EnrichSession is called after Redeem to allow providers to enrich session fields
105// such as User, Email, Groups with provider specific API calls.
106func (p *OIDCProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error {
107 // If a mandatory email wasn't set, error at this point.
109 return errors.New("neither the id_token nor the profileURL set an email")
114// ValidateSession checks that the session's id_token or access_token (when a ValidateURL is configured) is still valid
115func (p *OIDCProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
116 ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
118 // https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
119 // The ID Token is optional in the Refresh Token Response
121 validateEndpointAvailable := p.Data().ValidateURL != nil && p.Data().ValidateURL.String() != ""
122 if validateEndpointAvailable && !validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken)) {
123 logger.Errorf("access_token validation failed")
129 if _, err := p.Verifier.Verify(ctx, s.IDToken); err != nil {
130 logger.Errorf("id_token verification failed: %v", err)
138 if err := p.checkNonce(s); err != nil {
139 logger.Errorf("nonce verification failed: %v", err)
146// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
147func (p *OIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
148 if s == nil || s.RefreshToken == "" {
152 ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
153 err := p.redeemRefreshToken(ctx, s)
155 return false, fmt.Errorf("unable to redeem refresh token: %v", err)
161// redeemRefreshToken uses a RefreshToken with the RedeemURL to refresh the
162// Access Token and (optionally) the ID Token.
163// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
164func (p *OIDCProvider) redeemRefreshToken(ctx context.Context, s *sessions.SessionState) error {
165 clientSecret, err := p.GetClientSecret()
171 ClientID: p.ClientID,
172 ClientSecret: clientSecret,
173 Endpoint: oauth2.Endpoint{
174 TokenURL: p.RedeemURL.String(),
178 RefreshToken: s.RefreshToken,
179 Expiry: time.Now().Add(-time.Hour),
181 token, err := c.TokenSource(ctx, t).Token()
183 return fmt.Errorf("failed to get token: %v", err)
186 newSession, err := p.createSession(ctx, token, true)
188 return fmt.Errorf("unable create new session state from response: %v", err)
191 // It's possible that a refresh does not renew the ID Token.
192 // If it doesn't, it's probably better to retain the old one.
193 if newSession.IDToken != "" {
194 s.IDToken = newSession.IDToken
195 s.Email = newSession.Email
196 s.User = newSession.User
197 s.Groups = newSession.Groups
198 s.PreferredUsername = newSession.PreferredUsername
201 s.AccessToken = newSession.AccessToken
202 s.RefreshToken = newSession.RefreshToken
203 s.CreatedAt = newSession.CreatedAt
204 s.ExpiresOn = newSession.ExpiresOn
205 s.Refreshed = newSession.Refreshed
210// CreateSessionFromToken converts Bearer IDTokens into sessions
211func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) {
212 ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
213 idToken, err := p.Verifier.Verify(ctx, token)
218 ss, err := p.buildSessionFromClaims(token, "")
223 // Allow empty Email in Bearer case since we can't hit the ProfileURL
228 ss.AccessToken = token
233 ss.SetExpiresOn(idToken.Expiry)
238// createSession takes an oauth2.Token and creates a SessionState from it.
239// It alters behavior if called from Redeem vs Refresh
240func (p *OIDCProvider) createSession(ctx context.Context, token *oauth2.Token, refresh bool) (*sessions.SessionState, error) {
241 _, err := p.verifyIDToken(ctx, token)
244 case ErrMissingIDToken:
245 // IDToken is mandatory in Redeem but optional in Refresh
247 return nil, errors.New("token response did not contain an id_token")
250 return nil, fmt.Errorf("could not verify id_token: %v", err)
254 rawIDToken := getIDToken(token)
255 ss, err := p.buildSessionFromClaims(rawIDToken, token.AccessToken)
260 ss.AccessToken = token.AccessToken
261 ss.RefreshToken = token.RefreshToken
262 ss.IDToken = rawIDToken
265 ss.SetExpiresOn(token.Expiry)
266 ss.Refreshed = refresh
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list