Skip to content

Commit 2018840

Browse files
committed
Add PKCE (RFC 7636)
OAuth 2.0 security best current practice draft recommends using PKCE: https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-22.html#section-2.1.1-2.2.1 Signed-off-by: Oliver Eikemeier <[email protected]>
1 parent c1f799e commit 2018840

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

01-Login/web/app/callback/callback.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/gin-gonic/gin"
88

99
"01-Login/platform/authenticator"
10+
"01-Login/web/app/pkce"
1011
)
1112

1213
// Handler for our callback.
@@ -18,8 +19,11 @@ func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
1819
return
1920
}
2021

22+
verifier := session.Get("verifier").(string)
23+
exchangeOptions := pkce.ExchangeVerifier(verifier)
24+
2125
// Exchange an authorization code for a token.
22-
token, err := auth.Exchange(ctx.Request.Context(), ctx.Query("code"))
26+
token, err := auth.Exchange(ctx.Request.Context(), ctx.Query("code"), exchangeOptions...)
2327
if err != nil {
2428
ctx.String(http.StatusUnauthorized, "Failed to convert an authorization code into a token.")
2529
return

01-Login/web/app/login/login.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/gin-gonic/gin"
1010

1111
"01-Login/platform/authenticator"
12+
"01-Login/web/app/pkce"
1213
)
1314

1415
// Handler for our login.
@@ -20,15 +21,23 @@ func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
2021
return
2122
}
2223

24+
verifier, err := pkce.RandomVerifier(32)
25+
if err != nil {
26+
ctx.String(http.StatusInternalServerError, err.Error())
27+
return
28+
}
29+
2330
// Save the state inside the session.
2431
session := sessions.Default(ctx)
2532
session.Set("state", state)
33+
session.Set("verifier", verifier)
2634
if err := session.Save(); err != nil {
2735
ctx.String(http.StatusInternalServerError, err.Error())
2836
return
2937
}
3038

31-
ctx.Redirect(http.StatusTemporaryRedirect, auth.AuthCodeURL(state))
39+
authCodeOptions := pkce.Sha256Challenge(verifier)
40+
ctx.Redirect(http.StatusTemporaryRedirect, auth.AuthCodeURL(state, authCodeOptions...))
3241
}
3342
}
3443

01-Login/web/app/pkce/pkce.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package pkce
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/sha256"
6+
"encoding/base64"
7+
"fmt"
8+
9+
"golang.org/x/oauth2"
10+
)
11+
12+
const (
13+
codeChallengeKey = "code_challenge"
14+
codeChallengeMethodKey = "code_challenge_method"
15+
codeVerifierKey = "code_verifier"
16+
17+
challengeMethodSha256 = "S256"
18+
challengeMethodPlain = "plain"
19+
)
20+
21+
func RandomVerifier(length int) (string, error) {
22+
// RFC7636 4.1: Result must be between 43 and 128 characters.
23+
if length < 32 || length > 96 {
24+
return "", fmt.Errorf("expected length between 32 and 96, got %d", length)
25+
}
26+
27+
buf := make([]byte, length)
28+
if _, err := rand.Read(buf); err != nil {
29+
return "", err
30+
}
31+
return base64.RawURLEncoding.EncodeToString(buf), nil
32+
}
33+
34+
func Sha256Challenge(verifier string) []oauth2.AuthCodeOption {
35+
sha := sha256.Sum256([]byte(verifier))
36+
challenge := base64.RawURLEncoding.EncodeToString(sha[:])
37+
38+
return challengeOptions(challengeMethodSha256, challenge)
39+
}
40+
41+
func PlainChallenge(verifier string) []oauth2.AuthCodeOption {
42+
return challengeOptions(challengeMethodPlain, verifier)
43+
}
44+
45+
func challengeOptions(method string, challenge string) []oauth2.AuthCodeOption {
46+
return []oauth2.AuthCodeOption{
47+
oauth2.SetAuthURLParam(codeChallengeMethodKey, method),
48+
oauth2.SetAuthURLParam(codeChallengeKey, challenge),
49+
}
50+
}
51+
52+
func ExchangeVerifier(verifier string) []oauth2.AuthCodeOption {
53+
return []oauth2.AuthCodeOption{
54+
oauth2.SetAuthURLParam(codeVerifierKey, verifier),
55+
}
56+
}

0 commit comments

Comments
 (0)