Skip to content

Commit 040bd00

Browse files
committed
Add oidc pkce support
1 parent 26e7995 commit 040bd00

File tree

12 files changed

+440
-54
lines changed

12 files changed

+440
-54
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ OIDC Provider:
177177
--providers.oidc.client-id= Client ID [$PROVIDERS_OIDC_CLIENT_ID]
178178
--providers.oidc.client-secret= Client Secret [$PROVIDERS_OIDC_CLIENT_SECRET]
179179
--providers.oidc.resource= Optional resource indicator [$PROVIDERS_OIDC_RESOURCE]
180+
--providers.oidc.pkce_required= Optional pkce required indicator [$PROVIDERS_OIDC_PKCE_REQUIRED]
180181
181182
Generic OAuth2 Provider:
182183
--providers.generic-oauth.auth-url= Auth/Login URL [$PROVIDERS_GENERIC_OAUTH_AUTH_URL]

go.sum

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
814814
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
815815
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
816816
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
817+
<<<<<<< HEAD
817818
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
818819
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
819820
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
@@ -827,6 +828,73 @@ github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
827828
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
828829
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
829830
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
831+
=======
832+
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
833+
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
834+
github.com/libkermit/compose v0.0.0-20171122111507-c04e39c026ad/go.mod h1:GyCk/ifDcqsU1tsRMMWqXANnTtxzcwEWscb7j5qmblM=
835+
github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807/go.mod h1:std11u6pTaNwryy0Hy1dTQNdHKka1jNpflEieKtv5VE=
836+
github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591/go.mod h1:EBQ0jeOrBpOTkquwjmJl4W6z5xqlf5oA2LZfTqRNcO0=
837+
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
838+
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
839+
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
840+
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
841+
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
842+
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51/go.mod h1:RYmqHbhWwIz3z9eVmQ2rx82rulEMG0t+Q1bzfc9DYN4=
843+
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f/go.mod h1:8heskWJ5c0v5J9WH89ADhyal1DOZcayll8fSbhB+/9A=
844+
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
845+
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
846+
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
847+
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
848+
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
849+
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
850+
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
851+
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
852+
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
853+
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
854+
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
855+
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
856+
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
857+
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
858+
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
859+
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
860+
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
861+
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
862+
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
863+
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
864+
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
865+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
866+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
867+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
868+
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
869+
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
870+
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
871+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
872+
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
873+
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
874+
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
875+
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
876+
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
877+
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
878+
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
879+
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
880+
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
881+
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
882+
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
883+
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
884+
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
885+
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
886+
github.com/opencontainers/runc v1.0.0-rc8/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
887+
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
888+
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
889+
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
890+
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
891+
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
892+
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
893+
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
894+
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
895+
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
896+
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
897+
>>>>>>> 18c28ba (Add pkce verifier.go and remove external dependency)
830898
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
831899
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
832900
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=

internal/cookie/cookie.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cookie
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"time"
7+
)
8+
9+
// CookieStore interface defines methods for setting and getting cookies
10+
type CookieStore interface {
11+
SetCookie(name, value string)
12+
GetCookie(name string) (string, error)
13+
DeleteCookie(name string)
14+
}
15+
16+
// CookieStoreImpl is a concrete implementation of the CookieStore interface
17+
type CookieStoreImpl struct {
18+
writer http.ResponseWriter
19+
request *http.Request
20+
secure bool
21+
}
22+
23+
// NewCookieStore creates a new instance of CookieStoreImpl
24+
func NewCookieStore(w http.ResponseWriter, r *http.Request, secure bool) *CookieStoreImpl {
25+
return &CookieStoreImpl{
26+
writer: w,
27+
request: r,
28+
secure: secure,
29+
}
30+
}
31+
32+
// SetCookie sets a cookie with the given name, value, and attributes
33+
func (c *CookieStoreImpl) SetCookie(name, value string) {
34+
cookie := &http.Cookie{
35+
Name: name,
36+
Value: value,
37+
Path: "/",
38+
Secure: c.secure,
39+
HttpOnly: true,
40+
SameSite: http.SameSiteLaxMode,
41+
}
42+
43+
http.SetCookie(c.writer, cookie)
44+
}
45+
46+
// DeleteCookie removes a cookie with the given name
47+
func (c *CookieStoreImpl) DeleteCookie(name string) {
48+
cookie := &http.Cookie{
49+
Name: name,
50+
Value: "",
51+
Path: "/",
52+
MaxAge: -1,
53+
Expires: time.Unix(0, 0),
54+
}
55+
56+
http.SetCookie(c.writer, cookie)
57+
}
58+
59+
// GetCookie retrieves the value of the cookie with the given name
60+
func (c *CookieStoreImpl) GetCookie(name string) (string, error) {
61+
cookie, err := c.request.Cookie(name)
62+
if err != nil {
63+
return "", errors.New("cookie not found")
64+
}
65+
return cookie.Value, nil
66+
}

internal/pkce/verifier.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package pkce
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/sha256"
6+
"encoding/base64"
7+
"fmt"
8+
"io"
9+
)
10+
11+
type CodeVerifier struct {
12+
Value string
13+
}
14+
15+
func CreateCodeVerifier() (*CodeVerifier, error) {
16+
secureRandomString, err := generateSecureRandomString(32)
17+
if err != nil {
18+
return nil, err
19+
}
20+
return &CodeVerifier{
21+
Value: secureRandomString,
22+
}, nil
23+
}
24+
25+
func CreateCodeVerifierWithCode(code string) *CodeVerifier {
26+
return &CodeVerifier{
27+
Value: code,
28+
}
29+
}
30+
31+
func (v *CodeVerifier) String() string {
32+
return v.Value
33+
}
34+
35+
func (v *CodeVerifier) CodeChallengeS256() string {
36+
h := sha256.New()
37+
h.Write([]byte(v.Value))
38+
hash := h.Sum(nil)
39+
40+
return encode(hash)
41+
}
42+
43+
func GenerateNonce() (string, error) {
44+
return generateSecureRandomString(32)
45+
}
46+
47+
func generateSecureRandomString(length int) (string, error) {
48+
bytes := make([]byte, length)
49+
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
50+
return "", fmt.Errorf("failed to generate secure random string: %w", err)
51+
}
52+
return base64.RawURLEncoding.EncodeToString(bytes), nil
53+
}
54+
55+
func encode(msg []byte) string {
56+
encoded := base64.RawURLEncoding.EncodeToString(msg)
57+
return encoded
58+
}

internal/provider/generic_oauth.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net/http"
99

10+
"github.com/thomseddon/traefik-forward-auth/internal/cookie"
1011
"golang.org/x/oauth2"
1112
)
1213

@@ -52,12 +53,12 @@ func (o *GenericOAuth) Setup() error {
5253
}
5354

5455
// GetLoginURL provides the login url for the given redirect uri and state
55-
func (o *GenericOAuth) GetLoginURL(redirectURI, state string) string {
56-
return o.OAuthGetLoginURL(redirectURI, state)
56+
func (o *GenericOAuth) GetLoginURL(redirectURI, state string, _ cookie.CookieStore) (string, error) {
57+
return o.OAuthGetLoginURL(redirectURI, state), nil
5758
}
5859

5960
// ExchangeCode exchanges the given redirect uri and code for a token
60-
func (o *GenericOAuth) ExchangeCode(redirectURI, code string) (string, error) {
61+
func (o *GenericOAuth) ExchangeCode(redirectURI, code string, _ cookie.CookieStore) (string, error) {
6162
token, err := o.OAuthExchangeCode(redirectURI, code)
6263
if err != nil {
6364
return "", err

internal/provider/generic_oauth_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ func TestGenericOAuthGetLoginURL(t *testing.T) {
5353
}
5454

5555
// Check url
56-
uri, err := url.Parse(p.GetLoginURL("http://example.com/_oauth", "state"))
56+
loginURL, _ := p.GetLoginURL("http://example.com/_oauth", "state", nil)
57+
uri, err := url.Parse(loginURL)
5758
assert.Nil(err)
5859
assert.Equal("https", uri.Scheme)
5960
assert.Equal("provider.com", uri.Host)
@@ -104,7 +105,7 @@ func TestGenericOAuthExchangeCode(t *testing.T) {
104105
// AuthStyleInHeader is attempted
105106
p.Config.Endpoint.AuthStyle = oauth2.AuthStyleInParams
106107

107-
token, err := p.ExchangeCode("http://example.com/_oauth", "code")
108+
token, err := p.ExchangeCode("http://example.com/_oauth", "code", nil)
108109
assert.Nil(err)
109110
assert.Equal("123456789", token)
110111
}

internal/provider/google.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"net/http"
88
"net/url"
9+
10+
"github.com/thomseddon/traefik-forward-auth/internal/cookie"
911
)
1012

1113
// Google provider
@@ -53,7 +55,7 @@ func (g *Google) Setup() error {
5355
}
5456

5557
// GetLoginURL provides the login url for the given redirect uri and state
56-
func (g *Google) GetLoginURL(redirectURI, state string) string {
58+
func (g *Google) GetLoginURL(redirectURI, state string, _ cookie.CookieStore) (string, error) {
5759
q := url.Values{}
5860
q.Set("client_id", g.ClientID)
5961
q.Set("response_type", "code")
@@ -68,11 +70,11 @@ func (g *Google) GetLoginURL(redirectURI, state string) string {
6870
u = *g.LoginURL
6971
u.RawQuery = q.Encode()
7072

71-
return u.String()
73+
return u.String(), nil
7274
}
7375

7476
// ExchangeCode exchanges the given redirect uri and code for a token
75-
func (g *Google) ExchangeCode(redirectURI, code string) (string, error) {
77+
func (g *Google) ExchangeCode(redirectURI, code string, _ cookie.CookieStore) (string, error) {
7678
form := url.Values{}
7779
form.Set("client_id", g.ClientID)
7880
form.Set("client_secret", g.ClientSecret)

internal/provider/google_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ func TestGoogleGetLoginURL(t *testing.T) {
6868
}
6969

7070
// Check url
71-
uri, err := url.Parse(p.GetLoginURL("http://example.com/_oauth", "state"))
71+
loginUrl, _ := p.GetLoginURL("http://example.com/_oauth", "state", nil)
72+
uri, err := url.Parse(loginUrl)
7273
assert.Nil(err)
7374
assert.Equal("https", uri.Scheme)
7475
assert.Equal("google.com", uri.Host)
@@ -116,7 +117,7 @@ func TestGoogleExchangeCode(t *testing.T) {
116117
},
117118
}
118119

119-
token, err := p.ExchangeCode("http://example.com/_oauth", "code")
120+
token, err := p.ExchangeCode("http://example.com/_oauth", "code", nil)
120121
assert.Nil(err)
121122
assert.Equal("123456789", token)
122123
}

0 commit comments

Comments
 (0)