@@ -13,11 +13,36 @@ import (
13
13
"golang.org/x/oauth2"
14
14
)
15
15
16
+ const (
17
+ // Parameter keys for AuthCodeURL method to support PKCE.
18
+ codeChallengeKey = "code_challenge"
19
+ codeChallengeMethodKey = "code_challenge_method"
20
+
21
+ // Parameter key for Exchange method to support PKCE.
22
+ codeVerifierKey = "code_verifier"
23
+ )
24
+
25
+ // PKCEParams holds parameters to support PKCE.
26
+ type PKCEParams struct {
27
+ Challenge string // The unpadded, base64-url-encoded string of the encrypted code verifier.
28
+ ChallengeMethod string // The encryption method (ex. S256).
29
+ Verifier string // The original, non-encrypted secret.
30
+ }
31
+
16
32
// AuthorizationHandler is a 3-legged-OAuth helper that prompts
17
33
// the user for OAuth consent at the specified auth code URL
18
34
// and returns an auth code and state upon approval.
19
35
type AuthorizationHandler func (authCodeURL string ) (code string , state string , err error )
20
36
37
+ // TokenSourceWithPKCE is an enhanced version of TokenSource with PKCE support.
38
+ //
39
+ // The pkce parameter supports PKCE flow, which uses code challenge and code verifier
40
+ // to prevent CSRF attacks. A unique code challenge and code verifier should be generated
41
+ // by the caller at runtime. See https://www.oauth.com/oauth2-servers/pkce/ for more info.
42
+ func TokenSourceWithPKCE (ctx context.Context , config * oauth2.Config , state string , authHandler AuthorizationHandler , pkce * PKCEParams ) oauth2.TokenSource {
43
+ return oauth2 .ReuseTokenSource (nil , authHandlerSource {config : config , ctx : ctx , authHandler : authHandler , state : state , pkce : pkce })
44
+ }
45
+
21
46
// TokenSource returns an oauth2.TokenSource that fetches access tokens
22
47
// using 3-legged-OAuth flow.
23
48
//
@@ -33,24 +58,37 @@ type AuthorizationHandler func(authCodeURL string) (code string, state string, e
33
58
// and response before exchanging the auth code for OAuth token to prevent CSRF
34
59
// attacks.
35
60
func TokenSource (ctx context.Context , config * oauth2.Config , state string , authHandler AuthorizationHandler ) oauth2.TokenSource {
36
- return oauth2 . ReuseTokenSource ( nil , authHandlerSource { config : config , ctx : ctx , authHandler : authHandler , state : state } )
61
+ return TokenSourceWithPKCE ( ctx , config , state , authHandler , nil )
37
62
}
38
63
39
64
type authHandlerSource struct {
40
65
ctx context.Context
41
66
config * oauth2.Config
42
67
authHandler AuthorizationHandler
43
68
state string
69
+ pkce * PKCEParams
44
70
}
45
71
46
72
func (source authHandlerSource ) Token () (* oauth2.Token , error ) {
47
- url := source .config .AuthCodeURL (source .state )
73
+ // Step 1: Obtain auth code.
74
+ var authCodeUrlOptions []oauth2.AuthCodeOption
75
+ if source .pkce != nil && source .pkce .Challenge != "" && source .pkce .ChallengeMethod != "" {
76
+ authCodeUrlOptions = []oauth2.AuthCodeOption {oauth2 .SetAuthURLParam (codeChallengeKey , source .pkce .Challenge ),
77
+ oauth2 .SetAuthURLParam (codeChallengeMethodKey , source .pkce .ChallengeMethod )}
78
+ }
79
+ url := source .config .AuthCodeURL (source .state , authCodeUrlOptions ... )
48
80
code , state , err := source .authHandler (url )
49
81
if err != nil {
50
82
return nil , err
51
83
}
52
84
if state != source .state {
53
85
return nil , errors .New ("state mismatch in 3-legged-OAuth flow" )
54
86
}
55
- return source .config .Exchange (source .ctx , code )
87
+
88
+ // Step 2: Exchange auth code for access token.
89
+ var exchangeOptions []oauth2.AuthCodeOption
90
+ if source .pkce != nil && source .pkce .Verifier != "" {
91
+ exchangeOptions = []oauth2.AuthCodeOption {oauth2 .SetAuthURLParam (codeVerifierKey , source .pkce .Verifier )}
92
+ }
93
+ return source .config .Exchange (source .ctx , code , exchangeOptions ... )
56
94
}
0 commit comments