Skip to content

Commit 63c89b6

Browse files
committed
oauth2: support PKCE
1 parent 839de22 commit 63c89b6

File tree

2 files changed

+59
-2
lines changed

2 files changed

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

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 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+
func GenerateVerifier() string {
25+
// "RECOMMENDED that the output of a suitable random number generator be
26+
// used to create a 32-octet sequence. The octet sequence is then
27+
// base64url-encoded to produce a 43-octet URL-safe string to use as the
28+
// code verifier."
29+
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
30+
data := make([]byte, 32)
31+
if _, err := io.ReadFull(rand.Reader, data); err != nil {
32+
panic(err)
33+
}
34+
return base64.URLEncoding.EncodeToString(data)
35+
}
36+
37+
// S256ChallengeOption should be passed to Config.AuthCodeURL. It derives an
38+
// S256 code challenge from verifier following RFC 7636.
39+
func S256ChallengeOption(verifier string) AuthCodeOption {
40+
sha := sha256.Sum256([]byte(verifier))
41+
challenge := base64.URLEncoding.EncodeToString(sha[:])
42+
return challengeOption{challenge_method: "S256", challenge: challenge}
43+
}
44+
45+
type challengeOption struct{ challenge_method, challenge string }
46+
47+
func (p challengeOption) setValue(m url.Values) {
48+
m.Set(codeChallengeMethodKey, p.challenge_method)
49+
m.Set(codeChallengeKey, p.challenge)
50+
}
51+
52+
// VerifierOption should be passed to Config.Exchange.
53+
func VerifierOption(verifier string) AuthCodeOption {
54+
return setParam{k: codeVerifierKey, v: verifier}
55+
}

0 commit comments

Comments
 (0)