Skip to content

Commit 175c42a

Browse files
committed
oauth2: support PKCE
1 parent a835fc4 commit 175c42a

File tree

2 files changed

+69
-2
lines changed

2 files changed

+69
-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

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
// VerifierOption describes a PKCE code verifier. It should be
40+
// passed to Config.Exchange only.
41+
func VerifierOption(verifier string) AuthCodeOption {
42+
return setParam{k: codeVerifierKey, v: verifier}
43+
}
44+
45+
// S256ChallengeFromVerifier returns the PKCE code challenge from verifier with method S256.
46+
func S256ChallengeFromVerifier(verifier string) string {
47+
sha := sha256.Sum256([]byte(verifier))
48+
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sha[:])
49+
}
50+
51+
// S256ChallengeOption derives a PKCE code challenge from verifier with method
52+
// S256. It should be passed to Config.AuthCodeURL only.
53+
func S256ChallengeOption(verifier string) AuthCodeOption {
54+
return challengeOption{
55+
challenge_method: "S256",
56+
challenge: S256ChallengeFromVerifier(verifier),
57+
}
58+
}
59+
60+
type challengeOption struct{ challenge_method, challenge string }
61+
62+
func (p challengeOption) setValue(m url.Values) {
63+
m.Set(codeChallengeMethodKey, p.challenge_method)
64+
m.Set(codeChallengeKey, p.challenge)
65+
}

0 commit comments

Comments
 (0)