@@ -14,12 +14,15 @@ import (
1414 "github.com/oauth2-proxy/oauth2-proxy/pkg/requests"
1515)
1616
17+const emailClaim = "email"
18+
1719// OIDCProvider represents an OIDC based Identity Provider
1820type OIDCProvider struct {
1921 *ProviderData
2022
2123 Verifier *oidc.IDTokenVerifier
2224 AllowUnverifiedEmail bool
25+ UserIDClaim string
2326}
2427
2528// NewOIDCProvider initiates a new OIDCProvider
@@ -148,24 +151,15 @@ func (p *OIDCProvider) findVerifiedIDToken(ctx context.Context, token *oauth2.To
148151
149152func (p *OIDCProvider) createSessionState(token *oauth2.Token, idToken *oidc.IDToken) (*sessions.SessionState, error) {
150153
151− newSession := &sessions.SessionState{}
154+ var newSession *sessions.SessionState
152155
153− if idToken != nil {
154− claims, err := findClaimsFromIDToken(idToken, token.AccessToken, p.ProfileURL.String())
156+ if idToken == nil {
157+ newSession = &sessions.SessionState{}
158+ } else {
159+ var err error
160+ newSession, err = p.createSessionStateInternal(token.Extra("id_token").(string), idToken, token)
155161 if err != nil {
156− return nil, fmt.Errorf("couldn't extract claims from id_token (%e)", err)
157− }
158−
159− if claims != nil {
160−
161− if !p.AllowUnverifiedEmail && claims.Verified != nil && !*claims.Verified {
162− return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
163− }
164−
165− newSession.IDToken = token.Extra("id_token").(string)
166− newSession.Email = claims.Email
167− newSession.User = claims.Subject
168− newSession.PreferredUsername = claims.PreferredUsername
162+ return nil, err
169163 }
170164 }
171165
@@ -176,6 +170,52 @@ func (p *OIDCProvider) createSessionState(token *oauth2.Token, idToken *oidc.IDT
176170 return newSession, nil
177171}
178172
173+func (p *OIDCProvider) CreateSessionStateFromBearerToken(rawIDToken string, idToken *oidc.IDToken) (*sessions.SessionState, error) {
174+ newSession, err := p.createSessionStateInternal(rawIDToken, idToken, nil)
175+ if err != nil {
176+ return nil, err
177+ }
178+
179+ newSession.AccessToken = rawIDToken
180+ newSession.IDToken = rawIDToken
181+ newSession.RefreshToken = ""
182+ newSession.ExpiresOn = idToken.Expiry
183+
184+ return newSession, nil
185+}
186+
187+func (p *OIDCProvider) createSessionStateInternal(rawIDToken string, idToken *oidc.IDToken, token *oauth2.Token) (*sessions.SessionState, error) {
188+
189+ newSession := &sessions.SessionState{}
190+
191+ if idToken == nil {
192+ return newSession, nil
193+ }
194+ accessToken := ""
195+ if token != nil {
196+ accessToken = token.AccessToken
197+ }
198+
199+ claims, err := p.findClaimsFromIDToken(idToken, accessToken, p.ProfileURL.String())
200+ if err != nil {
201+ return nil, fmt.Errorf("couldn't extract claims from id_token (%e)", err)
202+ }
203+
204+ newSession.IDToken = rawIDToken
205+
206+ newSession.Email = claims.UserID // TODO Rename SessionState.Email to .UserID in the near future
207+
208+ newSession.User = claims.Subject
209+ newSession.PreferredUsername = claims.PreferredUsername
210+
211+ verifyEmail := (p.UserIDClaim == emailClaim) && !p.AllowUnverifiedEmail
212+ if verifyEmail && claims.Verified != nil && !*claims.Verified {
213+ return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.UserID)
214+ }
215+
216+ return newSession, nil
217+}
218+
179219// ValidateSessionState checks that the session's IDToken is still valid
180220func (p *OIDCProvider) ValidateSessionState(s *sessions.SessionState) bool {
181221 ctx := context.Background()
@@ -190,15 +230,25 @@ func getOIDCHeader(accessToken string) http.Header {
190230 return header
191231}
192232
193−func findClaimsFromIDToken(idToken *oidc.IDToken, accessToken string, profileURL string) (*OIDCClaims, error) {
233+func (p *OIDCProvider) findClaimsFromIDToken(idToken *oidc.IDToken, accessToken string, profileURL string) (*OIDCClaims, error) {
194234
195− // Extract custom claims.
196235 claims := &OIDCClaims{}
197− if err := idToken.Claims(claims); err != nil {
198− return nil, fmt.Errorf("failed to parse id_token claims: %v", err)
236+ // Extract default claims.
237+ if err := idToken.Claims(&claims); err != nil {
238+ return nil, fmt.Errorf("failed to parse default id_token claims: %v", err)
239+ }
240+ // Extract custom claims.
241+ if err := idToken.Claims(&claims.rawClaims); err != nil {
242+ return nil, fmt.Errorf("failed to parse all id_token claims: %v", err)
243+ }
244+
245+ userID := claims.rawClaims[p.UserIDClaim]
246+ if userID == nil {
247+ return nil, fmt.Errorf("claims did not contains the required user-id-claim '%s'", p.UserIDClaim)
199248 }
249+ claims.UserID = fmt.Sprint(userID)
200250
201− if claims.Email == "" {
251+ if p.UserIDClaim == emailClaim && claims.UserID == "" {
202252 if profileURL == "" {
203253 return nil, fmt.Errorf("id_token did not contain an email")
204254 }
@@ -223,15 +273,16 @@ func findClaimsFromIDToken(idToken *oidc.IDToken, accessToken string, profileURL
223273 return nil, fmt.Errorf("neither id_token nor userinfo endpoint contained an email")
224274 }
225275
226− claims.Email = email
276+ claims.UserID = email
227277 }
228278
229279 return claims, nil
230280}
231281
232282type OIDCClaims struct {
283+ rawClaims map[string]interface{}
284+ UserID string
233285 Subject string `json:"sub"`
234− Email string `json:"email"`
235286 Verified *bool `json:"email_verified"`
236287 PreferredUsername string `json:"preferred_username"`
237288}