Skip to content

Commit

Permalink
Merge branch 'oauth2_pkce'
Browse files Browse the repository at this point in the history
  • Loading branch information
sowiner committed Aug 9, 2023
2 parents 375b9f5 + 65b84a9 commit 0f4bee2
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 9 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/grokify/go-pkce v0.2.3
github.com/klauspost/compress v1.16.6 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/go-pkce v0.2.3 h1:L4VmAvavBAcmC4sz084ccmK21qo9y7it4ZEG76DgD0I=
github.com/grokify/go-pkce v0.2.3/go.mod h1:DABMww8Ue+sVrmOBDrt8dH8iFFUtSfmUCKOS3nh4ye8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
Expand Down
4 changes: 4 additions & 0 deletions provider/custom_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type CustomHandlerOpt struct {
MapUserFn func(UserData, []byte) token.User
BearerTokenHookFn BearerTokenHook
Scopes []string
AuthCodeOptions AuthCodeOption
ExchangeOptions AuthCodeOption
}

// CustomServerOpt are options to initialize a custom go-oauth2/oauth2 server
Expand Down Expand Up @@ -212,6 +214,8 @@ func NewCustom(name string, p Params, copts CustomHandlerOpt) Oauth2Handler {
infoURL: copts.InfoURL,
mapUser: copts.MapUserFn,
bearerTokenHook: copts.BearerTokenHookFn,
authCodeOptions: copts.AuthCodeOptions,
exchangeOptions: copts.ExchangeOptions,
})
}

Expand Down
28 changes: 26 additions & 2 deletions provider/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ type Oauth2Handler struct {
mapUser func(UserData, []byte) token.User // map info from InfoURL to User
bearerTokenHook BearerTokenHook // a way to get a Bearer token received from oauth2-provider
conf oauth2.Config
authCodeOptions AuthCodeOption
exchangeOptions AuthCodeOption
}

type AuthCodeOption func() map[string]string

// Params to make initialized and ready to use provider
type Params struct {
logger.L
Expand Down Expand Up @@ -128,8 +132,18 @@ func (p Oauth2Handler) LoginHandler(w http.ResponseWriter, r *http.Request) {
// e.g. http://localhost:8080/auth/github/callback
p.conf.RedirectURL = p.makeRedirURL(r.URL.Path)

// support PKCE challenge for oauth2 providers auth code flow
aco := make([]oauth2.AuthCodeOption, 0)
if p.authCodeOptions != nil {
ss := p.authCodeOptions()

for k, v := range ss {
aco = append(aco, oauth2.SetAuthURLParam(k, v))
}
}

// return login url
loginURL := p.conf.AuthCodeURL(state)
loginURL := p.conf.AuthCodeURL(state, aco...)
p.Logf("[DEBUG] login url %s, claims=%+v", loginURL, claims)

http.Redirect(w, r, loginURL, http.StatusFound)
Expand Down Expand Up @@ -158,7 +172,17 @@ func (p Oauth2Handler) AuthHandler(w http.ResponseWriter, r *http.Request) {
p.conf.RedirectURL = p.makeRedirURL(r.URL.Path)

p.Logf("[DEBUG] token with state %s", retrievedState)
tok, err := p.conf.Exchange(context.Background(), r.URL.Query().Get("code"))
// support PKCE challenge for oauth2 providers exchange code flow
aco := make([]oauth2.AuthCodeOption, 0)
if p.authCodeOptions != nil {
ss := p.authCodeOptions()

for k, v := range ss {
aco = append(aco, oauth2.SetAuthURLParam(k, v))
}
}

tok, err := p.conf.Exchange(context.Background(), r.URL.Query().Get("code"), aco...)
if err != nil {
rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "exchange failed")
return
Expand Down
55 changes: 48 additions & 7 deletions provider/oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/grokify/go-pkce"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
Expand All @@ -35,9 +36,28 @@ func (h *rememberLastBearerTokenHook) hook(s string, user token.User, o oauth2.T
h.LastToken = o
}

var codeVerifier string

func preAuthCodeOps() map[string]string {
// Create a code_verifier with default 32 byte length.
codeVerifier, _ = pkce.NewCodeVerifier(-1)

codeChallenge := pkce.CodeChallengeS256(codeVerifier)
return map[string]string{
"code_challenge": codeChallenge,
"code_challenge_method:": pkce.MethodS256,
}
}

func postAuthCodeOps() map[string]string {
return map[string]string{
"code_verifier": codeVerifier,
}
}

func TestOauth2Login(t *testing.T) {

teardown := prepOauth2Test(t, 8981, 8982, nil)
teardown := prepOauth2Test(t, 8981, 8982, nil, nil, nil)
defer teardown()

jar, err := cookiejar.New(nil)
Expand Down Expand Up @@ -91,7 +111,7 @@ func TestOauth2Login(t *testing.T) {
func TestOauth2LoginBearerTokenHook(t *testing.T) {

btHook := rememberLastBearerTokenHook{}
teardown := prepOauth2Test(t, 8981, 8982, btHook.hook)
teardown := prepOauth2Test(t, 8981, 8982, btHook.hook, nil, nil)
defer teardown()

jar, err := cookiejar.New(nil)
Expand All @@ -117,9 +137,28 @@ func TestOauth2LoginBearerTokenHook(t *testing.T) {
assert.Equal(t, "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", btHook.LastToken.AccessToken)
}

func TestOauth2LoginAuthCodeOption(t *testing.T) {

teardown := prepOauth2Test(t, 8981, 8982, nil, preAuthCodeOps, postAuthCodeOps)
defer teardown()

jar, err := cookiejar.New(nil)
require.Nil(t, err)
client := &http.Client{Jar: jar, Timeout: 5 * time.Second}

// check non-admin, permanent
resp, err := client.Get("http://localhost:8981/login?site=remark")
require.Nil(t, err)
assert.Equal(t, 200, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
t.Logf("resp %s", string(body))

}

func TestOauth2LoginSessionOnly(t *testing.T) {

teardown := prepOauth2Test(t, 8981, 8982, nil)
teardown := prepOauth2Test(t, 8981, 8982, nil, nil, nil)
defer teardown()

jar, err := cookiejar.New(nil)
Expand Down Expand Up @@ -153,7 +192,7 @@ func TestOauth2LoginSessionOnly(t *testing.T) {

func TestOauth2LoginNoAva(t *testing.T) {

teardown := prepOauth2Test(t, 8981, 8982, nil)
teardown := prepOauth2Test(t, 8981, 8982, nil, nil, nil)
defer teardown()

jar, err := cookiejar.New(nil)
Expand Down Expand Up @@ -188,7 +227,7 @@ func TestOauth2LoginNoAva(t *testing.T) {

func TestOauth2Logout(t *testing.T) {

teardown := prepOauth2Test(t, 8691, 8692, nil)
teardown := prepOauth2Test(t, 8691, 8692, nil, nil, nil)
defer teardown()

jar, err := cookiejar.New(nil)
Expand Down Expand Up @@ -228,7 +267,7 @@ func TestOauth2InitProvider(t *testing.T) {
}

func TestOauth2InvalidHandler(t *testing.T) {
teardown := prepOauth2Test(t, 8691, 8692, nil)
teardown := prepOauth2Test(t, 8691, 8692, nil, nil, nil)
defer teardown()

client := &http.Client{Timeout: 5 * time.Second}
Expand Down Expand Up @@ -258,7 +297,7 @@ func TestMakeRedirURL(t *testing.T) {
}
}

func prepOauth2Test(t *testing.T, loginPort, authPort int, btHook BearerTokenHook) func() {
func prepOauth2Test(t *testing.T, loginPort, authPort int, btHook BearerTokenHook, preaco, postaco AuthCodeOption) func() {

provider := Oauth2Handler{
name: "mock",
Expand All @@ -277,6 +316,8 @@ func prepOauth2Test(t *testing.T, loginPort, authPort int, btHook BearerTokenHoo
return userInfo
},
bearerTokenHook: btHook,
authCodeOptions: preaco,
exchangeOptions: postaco,
}

jwtService := token.NewService(token.Opts{
Expand Down

0 comments on commit 0f4bee2

Please sign in to comment.