Skip to content

Commit 6ea20b1

Browse files
committed
oauth2: support PKCE
1 parent ac6658e commit 6ea20b1

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

example_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ func ExampleConfig() {
2626
},
2727
}
2828

29+
verifier := oauth2.GenerateVerifier()
30+
2931
// Redirect user to consent page to ask for permission
3032
// for the scopes specified above.
31-
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
33+
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(verifier))
3234
fmt.Printf("Visit the URL for the auth dialog: %v", url)
3335

3436
// Use the authorization code that is pushed to the redirect
@@ -39,7 +41,7 @@ func ExampleConfig() {
3941
if _, err := fmt.Scan(&code); err != nil {
4042
log.Fatal(err)
4143
}
42-
tok, err := conf.Exchange(ctx, code)
44+
tok, err := conf.Exchange(ctx, code, oauth2.VerifierOption(verifier))
4345
if err != nil {
4446
log.Fatal(err)
4547
}

pkce.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2014 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
package oauth2
5+
6+
import (
7+
"crypto/rand"
8+
"crypto/sha256"
9+
"encoding/base64"
10+
"io"
11+
"net/url"
12+
)
13+
14+
const (
15+
codeChallengeKey = "code_challenge"
16+
codeChallengeMethodKey = "code_challenge_method"
17+
codeVerifierKey = "code_verifier"
18+
)
19+
20+
// GenerateVerifier generates a PKCE code verifier with 32 octets of randomness.
21+
// This follows recommendations in RFC 7636.
22+
//
23+
// A fresh verifier should be generated for each authorization.
24+
// S256ChallengeOption(verifier) should then be passed to Config.AuthCodeURL and
25+
// VerifierOption(verifier) to Config.Exchange.
26+
func GenerateVerifier() string {
27+
// "RECOMMENDED that the output of a suitable random number generator be
28+
// used to create a 32-octet sequence. The octet sequence is then
29+
// base64url-encoded to produce a 43-octet URL-safe string to use as the
30+
// code verifier."
31+
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
32+
data := make([]byte, 32)
33+
if _, err := io.ReadFull(rand.Reader, data); err != nil {
34+
panic(err)
35+
}
36+
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data)
37+
}
38+
39+
// S256ChallengeOption derives a PKCE code challenge from verifier with method
40+
// S256. It should be passed to Config.AuthCodeURL only.
41+
func S256ChallengeOption(verifier string) AuthCodeOption {
42+
sha := sha256.Sum256([]byte(verifier))
43+
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sha[:])
44+
return challengeOption{challenge_method: "S256", challenge: challenge}
45+
}
46+
47+
// VerifierOption describes a PKCE code verifier. It should be
48+
// passed to Config.Exchange only.
49+
func VerifierOption(verifier string) AuthCodeOption {
50+
return setParam{k: codeVerifierKey, v: verifier}
51+
}
52+
53+
type challengeOption struct{ challenge_method, challenge string }
54+
55+
func (p challengeOption) setValue(m url.Values) {
56+
m.Set(codeChallengeMethodKey, p.challenge_method)
57+
m.Set(codeChallengeKey, p.challenge)
58+
}

0 commit comments

Comments
 (0)