@@ -3,16 +3,25 @@ package provider
33import (
44 "context"
55 "errors"
6+ "strings"
67
78 "github.com/coreos/go-oidc"
9+ "github.com/thomseddon/traefik-forward-auth/internal/cookie"
10+ "github.com/thomseddon/traefik-forward-auth/internal/pkce"
811 "golang.org/x/oauth2"
912)
1013
14+ const (
15+ CookieNameNonce = "oidc-nonce"
16+ CookieNamePkceCode = "oidc-pkce-code"
17+ )
18+
1119// OIDC provider
1220type OIDC struct {
1321 IssuerURL string `long:"issuer-url" env:"ISSUER_URL" description:"Issuer URL"`
1422 ClientID string `long:"client-id" env:"CLIENT_ID" description:"Client ID"`
1523 ClientSecret string `long:"client-secret" env:"CLIENT_SECRET" description:"Client Secret" json:"-"`
24+ PkceRequired bool `long:"pkce-required" env:"PKCE_REQUIRED" description:"Optional pkce required indicator"`
1625
1726 OAuthProvider
1827
@@ -27,9 +36,9 @@ func (o *OIDC) Name() string {
2736
2837// Setup performs validation and setup
2938func (o * OIDC ) Setup () error {
30- // Check parms
31- if o . IssuerURL == "" || o . ClientID == "" || o . ClientSecret == "" {
32- return errors . New ( "providers.oidc.issuer-url, providers.oidc.client-id, providers.oidc.client-secret must be set" )
39+ // Check params
40+ if err := o . checkParams (); err != nil {
41+ return err
3342 }
3443
3544 var err error
@@ -60,13 +69,47 @@ func (o *OIDC) Setup() error {
6069}
6170
6271// GetLoginURL provides the login url for the given redirect uri and state
63- func (o * OIDC ) GetLoginURL (redirectURI , state string ) string {
64- return o .OAuthGetLoginURL (redirectURI , state )
72+ func (o * OIDC ) GetLoginURL (redirectURI , state string , cookieStore cookie.CookieStore ) (string , error ) {
73+ var opts []oauth2.AuthCodeOption
74+
75+ // Generate and store nonce
76+ nonce , err := pkce .GenerateNonce ()
77+ if err != nil {
78+ return "" , err
79+ }
80+
81+ cookieStore .SetCookie (CookieNameNonce , nonce )
82+
83+ opts = append (opts , oauth2 .SetAuthURLParam ("nonce" , nonce ))
84+
85+ if o .PkceRequired {
86+ pkceVerifier , err := pkce .CreateCodeVerifier ()
87+ if err != nil {
88+ return "" , err
89+ }
90+
91+ opts = append (opts , oauth2 .SetAuthURLParam ("code_challenge_method" , "S256" ))
92+ opts = append (opts , oauth2 .SetAuthURLParam ("code_challenge" , pkceVerifier .CodeChallengeS256 ()))
93+
94+ cookieStore .SetCookie (CookieNamePkceCode , pkceVerifier .String ())
95+ }
96+ return o .OAuthGetLoginURL (redirectURI , state , opts ... ), nil
6597}
6698
6799// ExchangeCode exchanges the given redirect uri and code for a token
68- func (o * OIDC ) ExchangeCode (redirectURI , code string ) (string , error ) {
69- token , err := o .OAuthExchangeCode (redirectURI , code )
100+ func (o * OIDC ) ExchangeCode (redirectURI , code string , cookieStore cookie.CookieStore ) (string , error ) {
101+ var opts []oauth2.AuthCodeOption
102+
103+ if o .PkceRequired {
104+ pkceCode , err := cookieStore .GetCookie (CookieNamePkceCode )
105+ if err != nil {
106+ return "" , err
107+ }
108+ cookieStore .DeleteCookie (CookieNamePkceCode )
109+ opts = append (opts , oauth2 .SetAuthURLParam ("code_verifier" , pkceCode ))
110+ }
111+
112+ token , err := o .OAuthExchangeCode (redirectURI , code , opts ... )
70113 if err != nil {
71114 return "" , err
72115 }
@@ -77,6 +120,23 @@ func (o *OIDC) ExchangeCode(redirectURI, code string) (string, error) {
77120 return "" , errors .New ("Missing id_token" )
78121 }
79122
123+ // Verify nonce
124+ idToken , err := o .verifier .Verify (o .ctx , rawIDToken )
125+ if err != nil {
126+ return "" , err
127+ }
128+
129+ nonce , err := cookieStore .GetCookie (CookieNameNonce )
130+ if err != nil {
131+ return "" , errors .New ("nonce not found" )
132+ }
133+
134+ cookieStore .DeleteCookie (CookieNameNonce )
135+
136+ if idToken .Nonce != nonce {
137+ return "" , errors .New ("nonce verification failed" )
138+ }
139+
80140 return rawIDToken , nil
81141}
82142
@@ -97,3 +157,25 @@ func (o *OIDC) GetUser(token string) (User, error) {
97157
98158 return user , nil
99159}
160+
161+ func (o * OIDC ) checkParams () error {
162+ if o .IssuerURL == "" || o .ClientID == "" || (o .ClientSecret == "" && ! o .PkceRequired ) {
163+ var emptyFields []string
164+
165+ if o .IssuerURL == "" {
166+ emptyFields = append (emptyFields , "providers.oidc.issuer-url" )
167+ }
168+
169+ if o .ClientID == "" {
170+ emptyFields = append (emptyFields , "providers.oidc.client-id" )
171+ }
172+
173+ if o .ClientSecret == "" && ! o .PkceRequired {
174+ emptyFields = append (emptyFields , "providers.oidc.client-secret" )
175+ }
176+
177+ return errors .New (strings .Join (emptyFields , ", " ) + " must be set" )
178+ }
179+
180+ return nil
181+ }
0 commit comments