git.iko.soy/typeable/oauth2-proxy
on master help sign in
Generalize and extend default CreateSessionFromToken
22f60e9Nick Meves
oauthproxy.go+49−63
@@ -13,7 +13,6 @@ import (
13 "strings"
13 "strings"
14 "time"
14 "time"
15
15
16− "github.com/coreos/go-oidc"
17 "github.com/justinas/alice"
16 "github.com/justinas/alice"
18 ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
17 ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
19 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
18 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
@@ -78,36 +77,34 @@ type OAuthProxy struct {
78 AuthOnlyPath string
77 AuthOnlyPath string
79 UserInfoPath string
78 UserInfoPath string
80
79
81− allowedRoutes []allowedRoute
80+ allowedRoutes []allowedRoute
82− redirectURL *url.URL // the url to receive requests at
81+ redirectURL *url.URL // the url to receive requests at
83− whitelistDomains []string
82+ whitelistDomains []string
84− provider providers.Provider
83+ provider providers.Provider
85− providerNameOverride string
84+ providerNameOverride string
86− sessionStore sessionsapi.SessionStore
85+ sessionStore sessionsapi.SessionStore
87− ProxyPrefix string
86+ ProxyPrefix string
88− SignInMessage string
87+ SignInMessage string
89− basicAuthValidator basic.Validator
88+ basicAuthValidator basic.Validator
90− displayHtpasswdForm bool
89+ displayHtpasswdForm bool
91− serveMux http.Handler
90+ serveMux http.Handler
92− SetXAuthRequest bool
91+ SetXAuthRequest bool
93− PassBasicAuth bool
92+ PassBasicAuth bool
94− SetBasicAuth bool
93+ SetBasicAuth bool
95− SkipProviderButton bool
94+ SkipProviderButton bool
96− PassUserHeaders bool
95+ PassUserHeaders bool
97− BasicAuthPassword string
96+ BasicAuthPassword string
98− PassAccessToken bool
97+ PassAccessToken bool
99− SetAuthorization bool
98+ SetAuthorization bool
100− PassAuthorization bool
99+ PassAuthorization bool
101− PreferEmailToUser bool
100+ PreferEmailToUser bool
102− skipAuthPreflight bool
101+ skipAuthPreflight bool
103− skipJwtBearerTokens bool
102+ skipJwtBearerTokens bool
104− mainJwtBearerVerifier *oidc.IDTokenVerifier
105− extraJwtBearerVerifiers []*oidc.IDTokenVerifier
106− templates *template.Template
103+ templates *template.Template
107− realClientIPParser ipapi.RealClientIPParser
104+ realClientIPParser ipapi.RealClientIPParser
108− trustedIPs *ip.NetSet
105+ trustedIPs *ip.NetSet
109− Banner string
106+ Banner string
110− Footer string
107+ Footer string
111
108
112 sessionChain alice.Chain
109 sessionChain alice.Chain
113 headersChain alice.Chain
110 headersChain alice.Chain
@@ -202,25 +199,23 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
202 AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
199 AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
203 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
200 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
204
201
205− ProxyPrefix: opts.ProxyPrefix,
202+ ProxyPrefix: opts.ProxyPrefix,
206− provider: opts.GetProvider(),
203+ provider: opts.GetProvider(),
207− providerNameOverride: opts.ProviderName,
204+ providerNameOverride: opts.ProviderName,
208− sessionStore: sessionStore,
205+ sessionStore: sessionStore,
209− serveMux: upstreamProxy,
206+ serveMux: upstreamProxy,
210− redirectURL: redirectURL,
207+ redirectURL: redirectURL,
211− allowedRoutes: allowedRoutes,
208+ allowedRoutes: allowedRoutes,
212− whitelistDomains: opts.WhitelistDomains,
209+ whitelistDomains: opts.WhitelistDomains,
213− skipAuthPreflight: opts.SkipAuthPreflight,
210+ skipAuthPreflight: opts.SkipAuthPreflight,
214− skipJwtBearerTokens: opts.SkipJwtBearerTokens,
211+ skipJwtBearerTokens: opts.SkipJwtBearerTokens,
215− mainJwtBearerVerifier: opts.GetOIDCVerifier(),
216− extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
217− realClientIPParser: opts.GetRealClientIPParser(),
212+ realClientIPParser: opts.GetRealClientIPParser(),
218− SkipProviderButton: opts.SkipProviderButton,
213+ SkipProviderButton: opts.SkipProviderButton,
219− templates: templates,
214+ templates: templates,
220− trustedIPs: trustedIPs,
215+ trustedIPs: trustedIPs,
221− Banner: opts.Banner,
216+ Banner: opts.Banner,
222− Footer: opts.Footer,
217+ Footer: opts.Footer,
223− SignInMessage: buildSignInMessage(opts),
218+ SignInMessage: buildSignInMessage(opts),
224
219
225 basicAuthValidator: basicAuthValidator,
220 basicAuthValidator: basicAuthValidator,
226 displayHtpasswdForm: basicAuthValidator != nil && opts.DisplayHtpasswdForm,
221 displayHtpasswdForm: basicAuthValidator != nil && opts.DisplayHtpasswdForm,
@@ -266,22 +261,13 @@ func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionSt
266 chain := alice.New()
261 chain := alice.New()
267
262
268 if opts.SkipJwtBearerTokens {
263 if opts.SkipJwtBearerTokens {
269− sessionLoaders := []middlewareapi.TokenToSessionLoader{}
264+ sessionLoaders := []middlewareapi.TokenToSessionFunc{
270− if opts.GetOIDCVerifier() != nil {
271− sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
272− Verifier: func(ctx context.Context, token string) (interface{}, error) {
273− return opts.GetOIDCVerifier().Verify(ctx, token)
274− },
275− TokenToSession: opts.GetProvider().CreateSessionFromToken,
265+ opts.GetProvider().CreateSessionFromToken,
276− })
277 }
266 }
278
267
279 for _, verifier := range opts.GetJWTBearerVerifiers() {
268 for _, verifier := range opts.GetJWTBearerVerifiers() {
280− sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
269+ sessionLoaders = append(sessionLoaders,
281− Verifier: func(ctx context.Context, token string) (interface{}, error) {
270+ middlewareapi.CreateTokenToSessionFunc(verifier.Verify))
282− return verifier.Verify(ctx, token)
283− },
284− })
285 }
271 }
286
272
287 chain = chain.Append(middleware.NewJwtSessionLoader(sessionLoaders))
273 chain = chain.Append(middleware.NewJwtSessionLoader(sessionLoaders))
pkg/apis/middleware/session.go+46−14
@@ -2,25 +2,57 @@ package middleware
2
2
3import (
3import (
4 "context"
4 "context"
5+ "fmt"
5
6
7+ "github.com/coreos/go-oidc"
6 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
8 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
7)
9)
8
10
9−// TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a
11+// TokenToSessionFunc takes a raw ID Token and converts it into a SessionState.
10−// SessionState.
11−type TokenToSessionFunc func(ctx context.Context, token string, verify VerifyFunc) (*sessionsapi.SessionState, error)
12+type TokenToSessionFunc func(ctx context.Context, token string) (*sessionsapi.SessionState, error)
12
13
13−// VerifyFunc takes a raw bearer token and verifies it
14+// VerifyFunc takes a raw bearer token and verifies it returning the converted
15+// oidc.IDToken representation of the token.
14−type VerifyFunc func(ctx context.Context, token string) (interface{}, error)
16+type VerifyFunc func(ctx context.Context, token string) (*oidc.IDToken, error)
15
17
16−// TokenToSessionLoader pairs a token verifier with the correct converter function
18+// CreateTokenToSessionFunc provides a handler that is a default implementation
17−// to convert the ID Token to a SessionState.
19+// for converting a JWT into a session.
18−type TokenToSessionLoader struct {
20+func CreateTokenToSessionFunc(verify VerifyFunc) TokenToSessionFunc {
19− // Verifier is used to verify that the ID Token was signed by the claimed issuer
21+ return func(ctx context.Context, token string) (*sessionsapi.SessionState, error) {
20− // and that the token has not been tampered with.
22+ var claims struct {
21− Verifier VerifyFunc
23+ Subject string `json:"sub"`
24+ Email string `json:"email"`
25+ Verified *bool `json:"email_verified"`
26+ PreferredUsername string `json:"preferred_username"`
27+ }
22
28
23− // TokenToSession converts a raw bearer token to a SessionState.
29+ idToken, err := verify(ctx, token)
24− // (Optional) If not set a default basic implementation is used.
30+ if err != nil {
25− TokenToSession TokenToSessionFunc
31+ return nil, err
32+ }
33+
34+ if err := idToken.Claims(&claims); err != nil {
35+ return nil, fmt.Errorf("failed to parse bearer token claims: %v", err)
36+ }
37+
38+ if claims.Email == "" {
39+ claims.Email = claims.Subject
40+ }
41+
42+ if claims.Verified != nil && !*claims.Verified {
43+ return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
44+ }
45+
46+ newSession := &sessionsapi.SessionState{
47+ Email: claims.Email,
48+ User: claims.Subject,
49+ PreferredUsername: claims.PreferredUsername,
50+ AccessToken: token,
51+ IDToken: token,
52+ RefreshToken: "",
53+ ExpiresOn: &idToken.Expiry,
54+ }
55+
56+ return newSession, nil
57+ }
26}
58}
pkg/middleware/jwt_session.go+3−59
@@ -1,12 +1,10 @@
1package middleware
1package middleware
2
2
3import (
3import (
4− "context"
5 "fmt"
4 "fmt"
6 "net/http"
5 "net/http"
7 "regexp"
6 "regexp"
8
7
9− "github.com/coreos/go-oidc"
10 "github.com/justinas/alice"
8 "github.com/justinas/alice"
11 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
12 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
10 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
@@ -16,16 +14,7 @@ import (
16
14
17const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`
15const jwtRegexFormat = `^ey[IJ][a-zA-Z0-9_-]*\.ey[IJ][a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`
18
16
19−func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) alice.Constructor {
17+func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionFunc) alice.Constructor {
20− for i, loader := range sessionLoaders {
21− if loader.TokenToSession == nil {
22− sessionLoaders[i] = middlewareapi.TokenToSessionLoader{
23− Verifier: loader.Verifier,
24− TokenToSession: createSessionFromToken,
25− }
26− }
27− }
28−
29 js := &jwtSessionLoader{
18 js := &jwtSessionLoader{
30 jwtRegex: regexp.MustCompile(jwtRegexFormat),
19 jwtRegex: regexp.MustCompile(jwtRegexFormat),
31 sessionLoaders: sessionLoaders,
20 sessionLoaders: sessionLoaders,
@@ -37,7 +26,7 @@ func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionLoader) al
37// Authorization headers.
26// Authorization headers.
38type jwtSessionLoader struct {
27type jwtSessionLoader struct {
39 jwtRegex *regexp.Regexp
28 jwtRegex *regexp.Regexp
40− sessionLoaders []middlewareapi.TokenToSessionLoader
29+ sessionLoaders []middlewareapi.TokenToSessionFunc
41}
30}
42
31
43// loadSession attempts to load a session from a JWT stored in an Authorization
32// loadSession attempts to load a session from a JWT stored in an Authorization
@@ -83,7 +72,7 @@ func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.Sessio
83
72
84 errs := []error{fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))}
73 errs := []error{fmt.Errorf("unable to verify jwt token: %q", req.Header.Get("Authorization"))}
85 for _, loader := range j.sessionLoaders {
74 for _, loader := range j.sessionLoaders {
86− session, err := loader.TokenToSession(req.Context(), token, loader.Verifier)
75+ session, err := loader(req.Context(), token)
87 if err == nil {
76 if err == nil {
88 return session, nil
77 return session, nil
89 } else {
78 } else {
@@ -135,48 +124,3 @@ func (j *jwtSessionLoader) getBasicToken(token string) (string, error) {
135
124
136 return "", fmt.Errorf("invalid basic auth token found in authorization header")
125 return "", fmt.Errorf("invalid basic auth token found in authorization header")
137}
126}
138−
139−// createSessionFromToken is a default implementation for converting
140−// a JWT into a session state.
141−func createSessionFromToken(ctx context.Context, token string, verify middlewareapi.VerifyFunc) (*sessionsapi.SessionState, error) {
142− var claims struct {
143− Subject string `json:"sub"`
144− Email string `json:"email"`
145− Verified *bool `json:"email_verified"`
146− PreferredUsername string `json:"preferred_username"`
147− }
148−
149− verifiedToken, err := verify(ctx, token)
150− if err != nil {
151− return nil, err
152− }
153−
154− idToken, ok := verifiedToken.(*oidc.IDToken)
155− if !ok {
156− return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token)
157− }
158−
159− if err := idToken.Claims(&claims); err != nil {
160− return nil, fmt.Errorf("failed to parse bearer token claims: %v", err)
161− }
162−
163− if claims.Email == "" {
164− claims.Email = claims.Subject
165− }
166−
167− if claims.Verified != nil && !*claims.Verified {
168− return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
169− }
170−
171− newSession := &sessionsapi.SessionState{
172− Email: claims.Email,
173− User: claims.Subject,
174− PreferredUsername: claims.PreferredUsername,
175− AccessToken: token,
176− IDToken: token,
177− RefreshToken: "",
178− ExpiresOn: &idToken.Expiry,
179− }
180−
181− return newSession, nil
182−}
pkg/middleware/jwt_session_test.go+29−38
@@ -26,7 +26,7 @@ import (
26type noOpKeySet struct {
26type noOpKeySet struct {
27}
27}
28
28
29−func (noOpKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {
29+func (noOpKeySet) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
30 splitStrings := strings.Split(jwt, ".")
30 splitStrings := strings.Split(jwt, ".")
31 payloadString := splitStrings[1]
31 payloadString := splitStrings[1]
32 return base64.RawURLEncoding.DecodeString(payloadString)
32 return base64.RawURLEncoding.DecodeString(payloadString)
@@ -78,16 +78,14 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
78 const nonVerifiedToken = validToken
78 const nonVerifiedToken = validToken
79
79
80 BeforeEach(func() {
80 BeforeEach(func() {
81− verifier = func(ctx context.Context, token string) (interface{}, error) {
82− return oidc.NewVerifier(
81+ verifier = oidc.NewVerifier(
83− "https://issuer.example.com",
82+ "https://issuer.example.com",
84− noOpKeySet{},
83+ noOpKeySet{},
85− &oidc.Config{
84+ &oidc.Config{
86− ClientID: "https://test.myapp.com",
85+ ClientID: "https://test.myapp.com",
87− SkipExpiryCheck: true,
86+ SkipExpiryCheck: true,
88− },
87+ },
89− ).Verify(ctx, token)
88+ ).Verify
90− }
91 })
89 })
92
90
93 type jwtSessionLoaderTableInput struct {
91 type jwtSessionLoaderTableInput struct {
@@ -110,10 +108,8 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
110
108
111 rw := httptest.NewRecorder()
109 rw := httptest.NewRecorder()
112
110
113− sessionLoaders := []middlewareapi.TokenToSessionLoader{
111+ sessionLoaders := []middlewareapi.TokenToSessionFunc{
114− {
112+ middlewareapi.CreateTokenToSessionFunc(verifier),
115− Verifier: verifier,
116− },
117 }
113 }
118
114
119 // Create the handler with a next handler that will capture the session
115 // Create the handler with a next handler that will capture the session
@@ -175,24 +171,19 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
175 const nonVerifiedToken = validToken
171 const nonVerifiedToken = validToken
176
172
177 BeforeEach(func() {
173 BeforeEach(func() {
178− verifier := func(ctx context.Context, token string) (interface{}, error) {
179− return oidc.NewVerifier(
174+ verifier := oidc.NewVerifier(
180− "https://issuer.example.com",
175+ "https://issuer.example.com",
181− noOpKeySet{},
176+ noOpKeySet{},
182− &oidc.Config{
177+ &oidc.Config{
183− ClientID: "https://test.myapp.com",
178+ ClientID: "https://test.myapp.com",
184− SkipExpiryCheck: true,
179+ SkipExpiryCheck: true,
185− },
180+ },
186− ).Verify(ctx, token)
181+ ).Verify
187− }
188
182
189 j = &jwtSessionLoader{
183 j = &jwtSessionLoader{
190 jwtRegex: regexp.MustCompile(jwtRegexFormat),
184 jwtRegex: regexp.MustCompile(jwtRegexFormat),
191− sessionLoaders: []middlewareapi.TokenToSessionLoader{
185+ sessionLoaders: []middlewareapi.TokenToSessionFunc{
192− {
186+ middlewareapi.CreateTokenToSessionFunc(verifier),
193− Verifier: verifier,
194− TokenToSession: createSessionFromToken,
195− },
196 },
187 },
197 }
188 }
198 })
189 })
@@ -402,7 +393,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
402 )
393 )
403 })
394 })
404
395
405− Context("createSessionFromToken", func() {
396+ Context("CreateTokenToSessionFunc", func() {
406 ctx := context.Background()
397 ctx := context.Background()
407 expiresFuture := time.Now().Add(time.Duration(5) * time.Minute)
398 expiresFuture := time.Now().Add(time.Duration(5) * time.Minute)
408 verified := true
399 verified := true
@@ -414,7 +405,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
414 jwt.StandardClaims
405 jwt.StandardClaims
415 }
406 }
416
407
417− type createSessionStateTableInput struct {
408+ type tokenToSessionTableInput struct {
418 idToken idTokenClaims
409 idToken idTokenClaims
419 expectedErr error
410 expectedErr error
420 expectedUser string
411 expectedUser string
@@ -423,8 +414,8 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
423 }
414 }
424
415
425 DescribeTable("when creating a session from an IDToken",
416 DescribeTable("when creating a session from an IDToken",
426− func(in createSessionStateTableInput) {
417+ func(in tokenToSessionTableInput) {
427− verifier := func(ctx context.Context, token string) (interface{}, error) {
418+ verifier := func(ctx context.Context, token string) (*oidc.IDToken, error) {
428 oidcVerifier := oidc.NewVerifier(
419 oidcVerifier := oidc.NewVerifier(
429 "https://issuer.example.com",
420 "https://issuer.example.com",
430 noOpKeySet{},
421 noOpKeySet{},
@@ -443,7 +434,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
443 rawIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS256, in.idToken).SignedString(key)
434 rawIDToken, err := jwt.NewWithClaims(jwt.SigningMethodRS256, in.idToken).SignedString(key)
444 Expect(err).ToNot(HaveOccurred())
435 Expect(err).ToNot(HaveOccurred())
445
436
446− session, err := createSessionFromToken(ctx, rawIDToken, verifier)
437+ session, err := middlewareapi.CreateTokenToSessionFunc(verifier)(ctx, rawIDToken)
447 if in.expectedErr != nil {
438 if in.expectedErr != nil {
448 Expect(err).To(MatchError(in.expectedErr))
439 Expect(err).To(MatchError(in.expectedErr))
449 Expect(session).To(BeNil())
440 Expect(session).To(BeNil())
@@ -459,7 +450,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
459 Expect(session.RefreshToken).To(BeEmpty())
450 Expect(session.RefreshToken).To(BeEmpty())
460 Expect(session.PreferredUsername).To(BeEmpty())
451 Expect(session.PreferredUsername).To(BeEmpty())
461 },
452 },
462− Entry("with no email", createSessionStateTableInput{
453+ Entry("with no email", tokenToSessionTableInput{
463 idToken: idTokenClaims{
454 idToken: idTokenClaims{
464 StandardClaims: jwt.StandardClaims{
455 StandardClaims: jwt.StandardClaims{
465 Audience: "asdf1234",
456 Audience: "asdf1234",
@@ -476,7 +467,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
476 expectedEmail: "123456789",
467 expectedEmail: "123456789",
477 expectedExpires: &expiresFuture,
468 expectedExpires: &expiresFuture,
478 }),
469 }),
479− Entry("with a verified email", createSessionStateTableInput{
470+ Entry("with a verified email", tokenToSessionTableInput{
480 idToken: idTokenClaims{
471 idToken: idTokenClaims{
481 StandardClaims: jwt.StandardClaims{
472 StandardClaims: jwt.StandardClaims{
482 Audience: "asdf1234",
473 Audience: "asdf1234",
@@ -495,7 +486,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
495 expectedEmail: "foo@example.com",
486 expectedEmail: "foo@example.com",
496 expectedExpires: &expiresFuture,
487 expectedExpires: &expiresFuture,
497 }),
488 }),
498− Entry("with a non-verified email", createSessionStateTableInput{
489+ Entry("with a non-verified email", tokenToSessionTableInput{
499 idToken: idTokenClaims{
490 idToken: idTokenClaims{
500 StandardClaims: jwt.StandardClaims{
491 StandardClaims: jwt.StandardClaims{
501 Audience: "asdf1234",
492 Audience: "asdf1234",
pkg/validation/options.go+5−6
@@ -233,6 +233,9 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
233 p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
233 p.ValidateURL, msgs = parseURL(o.ValidateURL, "validate", msgs)
234 p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
234 p.ProtectedResource, msgs = parseURL(o.ProtectedResource, "resource", msgs)
235
235
236+ // Make the OIDC Verifier accessible to all providers that can support it
237+ p.Verifier = o.GetOIDCVerifier()
238+
236 p.SetAllowedGroups(o.AllowedGroups)
239 p.SetAllowedGroups(o.AllowedGroups)
237
240
238 provider := providers.New(o.ProviderType, p)
241 provider := providers.New(o.ProviderType, p)
@@ -273,18 +276,14 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
273 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
276 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
274 p.UserIDClaim = o.UserIDClaim
277 p.UserIDClaim = o.UserIDClaim
275 p.GroupsClaim = o.OIDCGroupsClaim
278 p.GroupsClaim = o.OIDCGroupsClaim
276− if o.GetOIDCVerifier() == nil {
279+ if p.Verifier == nil {
277 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
280 msgs = append(msgs, "oidc provider requires an oidc issuer URL")
278− } else {
279− p.Verifier = o.GetOIDCVerifier()
280 }
281 }
281 case *providers.GitLabProvider:
282 case *providers.GitLabProvider:
282 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
283 p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
283 p.Groups = o.GitLabGroup
284 p.Groups = o.GitLabGroup
284
285
285− if o.GetOIDCVerifier() != nil {
286− p.Verifier = o.GetOIDCVerifier()
286+ if p.Verifier == nil {
287− } else {
288 // Initialize with default verifier for gitlab.com
287 // Initialize with default verifier for gitlab.com
289 ctx := context.Background()
288 ctx := context.Background()
290
289
providers/oidc.go+2−9
@@ -11,7 +11,6 @@ import (
11 oidc "github.com/coreos/go-oidc"
11 oidc "github.com/coreos/go-oidc"
12 "golang.org/x/oauth2"
12 "golang.org/x/oauth2"
13
13
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
@@ -23,7 +22,6 @@ const emailClaim = "email"
23type OIDCProvider struct {
22type OIDCProvider struct {
24 *ProviderData
23 *ProviderData
25
24
26− Verifier *oidc.IDTokenVerifier
27 AllowUnverifiedEmail bool
25 AllowUnverifiedEmail bool
28 UserIDClaim string
26 UserIDClaim string
29 GroupsClaim string
27 GroupsClaim string
@@ -176,17 +174,12 @@ func (p *OIDCProvider) createSessionState(ctx context.Context, token *oauth2.Tok
176 return newSession, nil
174 return newSession, nil
177}
175}
178
176
179−func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error) {
177+func (p *OIDCProvider) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) {
180− verifiedToken, err := verify(ctx, token)
178+ idToken, err := p.Verifier.Verify(ctx, token)
181 if err != nil {
179 if err != nil {
182 return nil, err
180 return nil, err
183 }
181 }
184
182
185− idToken, ok := verifiedToken.(*oidc.IDToken)
186− if !ok {
187− return nil, fmt.Errorf("failed to create IDToken from bearer token: %s", token)
188− }
189−
190 newSession, err := p.createSessionStateInternal(ctx, idToken, nil)
183 newSession, err := p.createSessionStateInternal(ctx, idToken, nil)
191 if err != nil {
184 if err != nil {
192 return nil, err
185 return nil, err
providers/oidc_test.go+7−17
@@ -144,16 +144,17 @@ func newOIDCProvider(serverURL *url.URL) *OIDCProvider {
144 Scheme: serverURL.Scheme,
144 Scheme: serverURL.Scheme,
145 Host: serverURL.Host,
145 Host: serverURL.Host,
146 Path: "/api"},
146 Path: "/api"},
147− Scope: "openid profile offline_access"}
147+ Scope: "openid profile offline_access",
148−
149− p := &OIDCProvider{
150− ProviderData: providerData,
151 Verifier: oidc.NewVerifier(
148 Verifier: oidc.NewVerifier(
152 "https://issuer.example.com",
149 "https://issuer.example.com",
153 fakeKeySetStub{},
150 fakeKeySetStub{},
154 &oidc.Config{ClientID: clientID},
151 &oidc.Config{ClientID: clientID},
155 ),
152 ),
153+ }
154+
155+ p := &OIDCProvider{
156+ ProviderData: providerData,
156− UserIDClaim: "email",
157+ UserIDClaim: "email",
157 }
158 }
158
159
159 return p
160 return p
@@ -347,18 +348,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
347 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
348 rawIDToken, err := newSignedTestIDToken(tc.IDToken)
348 assert.NoError(t, err)
349 assert.NoError(t, err)
349
350
350− verifyFunc := func(ctx context.Context, token string) (interface{}, error) {
351− keyset := fakeKeySetStub{}
352− verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
353− &oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
354−
355− idToken, err := verifier.Verify(ctx, token)
356− assert.NoError(t, err)
357−
358− return idToken, nil
359− }
360−
361− ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken, verifyFunc)
351+ ss, err := provider.CreateSessionFromToken(context.Background(), rawIDToken)
362 assert.NoError(t, err)
352 assert.NoError(t, err)
363
353
364 assert.Equal(t, tc.ExpectedUser, ss.User)
354 assert.Equal(t, tc.ExpectedUser, ss.User)
providers/provider_data.go+2
@@ -5,6 +5,7 @@ import (
5 "io/ioutil"
5 "io/ioutil"
6 "net/url"
6 "net/url"
7
7
8+ "github.com/coreos/go-oidc"
8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
9)
10)
10
11
@@ -26,6 +27,7 @@ type ProviderData struct {
26 ClientSecretFile string
27 ClientSecretFile string
27 Scope string
28 Scope string
28 Prompt string
29 Prompt string
30+ Verifier *oidc.IDTokenVerifier
29
31
30 // Universal Group authorization data structure
32 // Universal Group authorization data structure
31 // any provider can set to consume
33 // any provider can set to consume
providers/provider_default.go+4−1
@@ -126,6 +126,9 @@ func (p *ProviderData) RefreshSessionIfNeeded(_ context.Context, _ *sessions.Ses
126
126
127// CreateSessionStateFromBearerToken should be implemented to allow providers
127// CreateSessionStateFromBearerToken should be implemented to allow providers
128// to convert ID tokens into sessions
128// to convert ID tokens into sessions
129−func (p *ProviderData) CreateSessionFromToken(_ context.Context, _ string, _ middleware.VerifyFunc) (*sessions.SessionState, error) {
129+func (p *ProviderData) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) {
130+ if p.Verifier != nil {
131+ return middleware.CreateTokenToSessionFunc(p.Verifier.Verify)(ctx, token)
132+ }
130 return nil, ErrNotImplemented
133 return nil, ErrNotImplemented
131}
134}
providers/providers.go+1−2
@@ -3,7 +3,6 @@ package providers
3import (
3import (
4 "context"
4 "context"
5
5
6− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
8)
7)
9
8
@@ -18,7 +17,7 @@ type Provider interface {
18 ValidateSession(ctx context.Context, s *sessions.SessionState) bool
17 ValidateSession(ctx context.Context, s *sessions.SessionState) bool
19 GetLoginURL(redirectURI, finalRedirect string) string
18 GetLoginURL(redirectURI, finalRedirect string) string
20 RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error)
19 RefreshSessionIfNeeded(ctx context.Context, s *sessions.SessionState) (bool, error)
21− CreateSessionFromToken(ctx context.Context, token string, verify middleware.VerifyFunc) (*sessions.SessionState, error)
20+ CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error)
22}
21}
23
22
24// New provides a new Provider based on the configured provider string
23// New provides a new Provider based on the configured provider string