@@ -11,7 +11,11 @@ package oauth2 // import "golang.org/x/oauth2"
11
11
import (
12
12
"bytes"
13
13
"context"
14
+ "crypto/rand"
15
+ "crypto/sha256"
16
+ "encoding/base64"
14
17
"errors"
18
+ "io"
15
19
"net/http"
16
20
"net/url"
17
21
"strings"
@@ -120,7 +124,7 @@ var (
120
124
ApprovalForce AuthCodeOption = SetAuthURLParam ("prompt" , "consent" )
121
125
)
122
126
123
- // An AuthCodeOption is passed to Config.AuthCodeURL.
127
+ // An AuthCodeOption may be passed to Config.AuthCodeURL or Config.Exchange .
124
128
type AuthCodeOption interface {
125
129
setValue (url.Values )
126
130
}
@@ -138,15 +142,14 @@ func SetAuthURLParam(key, value string) AuthCodeOption {
138
142
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
139
143
// that asks for permissions for the required scopes explicitly.
140
144
//
141
- // State is a token to protect the user from CSRF attacks. You must
142
- // always provide a non-empty string and validate that it matches the
143
- // the state query parameter on your redirect callback.
144
- // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
145
+ // State is an opaque value used by the client to maintain state
146
+ // between the request and callback.
145
147
//
146
148
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
147
149
// as ApprovalForce.
148
- // It can also be used to pass the PKCE challenge.
149
- // See https://www.oauth.com/oauth2-servers/pkce/ for more info.
150
+ //
151
+ // To protect against cross-site request forgery (CSRF),
152
+ // it is recommended to pass PKCEParams.ChallengeOption().
150
153
func (c * Config ) AuthCodeURL (state string , opts ... AuthCodeOption ) string {
151
154
var buf bytes.Buffer
152
155
buf .WriteString (c .Endpoint .AuthURL )
@@ -161,7 +164,6 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
161
164
v .Set ("scope" , strings .Join (c .Scopes , " " ))
162
165
}
163
166
if state != "" {
164
- // TODO(light): Docs say never to omit state; don't allow empty.
165
167
v .Set ("state" , state )
166
168
}
167
169
for _ , opt := range opts {
@@ -379,3 +381,55 @@ func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
379
381
new : src ,
380
382
}
381
383
}
384
+
385
+ // PKCEParams holds a PKCE challenge and verifier as described in RFC 7636
386
+ // https://datatracker.ietf.org/doc/html/rfc7636
387
+ type PKCEParams struct {
388
+ Challenge string
389
+ ChallengeMethod string
390
+ Verifier string
391
+ }
392
+
393
+ const (
394
+ codeChallengeKey = "code_challenge"
395
+ codeChallengeMethodKey = "code_challenge_method"
396
+ codeVerifierKey = "code_verifier"
397
+ )
398
+
399
+ // ChallengeOption should be passed to Config.AuthCodeURL. The option returned sets the code_challenge and code_challenge_method parameters.
400
+ func (p * PKCEParams ) ChallengeOption () AuthCodeOption {
401
+ return set2Values {k1 : codeChallengeKey , v1 : p .Challenge , k2 : codeChallengeMethodKey , v2 : p .ChallengeMethod }
402
+ }
403
+
404
+ type set2Values struct { k1 , v1 , k2 , v2 string }
405
+
406
+ func (p set2Values ) setValue (m url.Values ) {
407
+ m .Set (p .k1 , p .v1 )
408
+ m .Set (p .k2 , p .v2 )
409
+ }
410
+
411
+ // VerifierOption should be passed to Config.Exchange. The option returned sets the code_verifier parameters.
412
+ func (p * PKCEParams ) VerifierOption () AuthCodeOption {
413
+ return SetAuthURLParam (codeVerifierKey , p .Verifier )
414
+ }
415
+
416
+ // GeneratePKCEParams generates a code verifier with 32 octets of randomness and a S256 challenge (this follows recommendations in RFC 7636).
417
+ //
418
+ // A fresh verifier should be generated for each AuthCodeURL call.
419
+ func GeneratePKCEParams () * PKCEParams {
420
+ // "RECOMMENDED that the output of a suitable random number generator be used to create a 32-octet
421
+ // sequence. The octet sequence is then base64url-encoded to produce a 43-octet URL-safe string to use as the code verifier."
422
+ // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
423
+ data := make ([]byte , 32 )
424
+ if _ , err := io .ReadFull (rand .Reader , data ); err != nil {
425
+ panic (err )
426
+ }
427
+ verifier := base64 .URLEncoding .EncodeToString (data )
428
+ sha := sha256 .Sum256 ([]byte (verifier ))
429
+ challenge := base64 .URLEncoding .EncodeToString (sha [:])
430
+ return & PKCEParams {
431
+ Challenge : challenge ,
432
+ ChallengeMethod : "S256" ,
433
+ Verifier : verifier ,
434
+ }
435
+ }
0 commit comments