Skip to content

Commit

Permalink
feat(core/auth/oidc): Add support of AuthCodeOption in the token ex…
Browse files Browse the repository at this point in the history
…change during callback (#338)

* feat(core/auth/oidc): Add support of `AuthCodeOption` in the token exchange during callback

Currently, it is only possible to modify the authorization URL using the `AuthCodeOptioner` interface. Since the Exchange function also supports this pattern we should also pass the registered `AuthCodeOptioner` to it.

For example, both my authorization and my token retrieval endpoint need some custom URL param to work. Currently, only the one for the auth endpoint would be possible.

---------

Co-authored-by: Karol Nowak <karol.nowak@omnevo.net>
  • Loading branch information
carstendietrich and Karol Nowak authored May 19, 2023
1 parent 5943eb6 commit b2453ba
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
10 changes: 9 additions & 1 deletion core/auth/oauth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,15 @@ func (i *openIDIdentifier) Callback(ctx context.Context, request *web.Request, r
return i.responder.ServerError(err)
}

oauth2Token, err := oauthConfig.Exchange(ctx, code)
options := make([]oauth2.AuthCodeOption, 0)

if i.authCodeOptionerProvider != nil {
for _, o := range i.authCodeOptionerProvider() {
options = append(options, o.Options(ctx, i.Broker(), request)...)
}
}

oauth2Token, err := oauthConfig.Exchange(ctx, code, options...)
if err != nil {
return i.responder.ServerError(err)
}
Expand Down
73 changes: 70 additions & 3 deletions core/auth/oauth/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
"testing"
"time"

"flamingo.me/flamingo/v3/core/auth"
"flamingo.me/flamingo/v3/framework/flamingo"
"flamingo.me/flamingo/v3/framework/web"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"

"flamingo.me/flamingo/v3/core/auth"
"flamingo.me/flamingo/v3/framework/flamingo"
"flamingo.me/flamingo/v3/framework/web"
)

type mockRouter struct {
Expand Down Expand Up @@ -138,6 +139,12 @@ func (p *testOidcProvider) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
}

type testAuthCodeOptioner struct{}

func (*testAuthCodeOptioner) Options(_ context.Context, _ string, req *web.Request) []oauth2.AuthCodeOption {
return []oauth2.AuthCodeOption{oauth2.SetAuthURLParam("redirect_uri", "foobar123test")}
}

func TestOidcCallback(t *testing.T) {
t.Run("Test Callback", func(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -233,6 +240,66 @@ func TestOidcCallback(t *testing.T) {
expectedURL, _ := url.Parse("https://example.com/callback-error-handler")
assert.Equal(t, result, &web.URLRedirectResponse{URL: expectedURL}, "Result of callback handler was ignored")
})

t.Run("Test add auth code option to exchange token call", func(t *testing.T) {
t.Parallel()

testServer := httptest.NewUnstartedServer(nil)
tokenCalled := false

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch strings.Trim(r.URL.Path, "/") {
case "token":
{
_ = r.ParseForm()
redirectURI := r.PostForm.Get("redirect_uri")

assert.Equal(t, redirectURI, "foobar123test")
tokenCalled = true

w.Header().Set("Content-type", "application/json")
return
}
case ".well-known/openid-configuration":
_, _ = fmt.Fprintf(w, `{
"issuer": "%s",
"token_endpoint": "%s/token",
"jwks_uri": "%s/certs"
}`, testServer.URL, testServer.URL, testServer.URL)
}
})

testServer.Config.Handler = handler
testServer.Start()
defer testServer.Close()

identifier := new(openIDIdentifier)
identifier.reverseRouter = new(mockRouter)
identifier.responder = new(web.Responder)

identifier.authCodeOptionerProvider = func() []AuthCodeOptioner {
return []AuthCodeOptioner{new(testAuthCodeOptioner)}
}

var err error
identifier.provider, err = oidc.NewProvider(context.Background(), testServer.URL)
assert.NoError(t, err)
identifier.oauth2Config = &oauth2.Config{
Endpoint: oauth2.Endpoint{AuthURL: "", TokenURL: testServer.URL + "/token"},
}

session := web.EmptySession()
request := web.CreateRequest(nil, session)

identifier.createSessionCode(request, "test-callback-state")

request.Request().URL.RawQuery = "state=test-callback-state&code=test-callback-code"
identifier.Callback(context.Background(), request, func(request *web.Request) *url.URL {
return new(url.URL)
})

assert.True(t, tokenCalled)
})
}

func Test_openIDIdentifier_RefreshIdentity(t *testing.T) {
Expand Down

0 comments on commit b2453ba

Please sign in to comment.