Skip to content

Commit 5c8e792

Browse files
authored
feat: add authentication options to hooks (#3633)
1 parent 3615e3d commit 5c8e792

File tree

11 files changed

+250
-115
lines changed

11 files changed

+250
-115
lines changed

driver/config/provider.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package config
55

66
import (
77
"context"
8+
"encoding/json"
89
"fmt"
910
"net/http"
1011
"net/url"
@@ -102,8 +103,8 @@ const (
102103
KeyOAuth2GrantJWTIDOptional = "oauth2.grant.jwt.jti_optional"
103104
KeyOAuth2GrantJWTIssuedDateOptional = "oauth2.grant.jwt.iat_optional"
104105
KeyOAuth2GrantJWTMaxDuration = "oauth2.grant.jwt.max_ttl"
105-
KeyRefreshTokenHookURL = "oauth2.refresh_token_hook" // #nosec G101
106-
KeyTokenHookURL = "oauth2.token_hook" // #nosec G101
106+
KeyRefreshTokenHook = "oauth2.refresh_token_hook" // #nosec G101
107+
KeyTokenHook = "oauth2.token_hook" // #nosec G101
107108
KeyDevelopmentMode = "dev"
108109
)
109110

@@ -467,12 +468,52 @@ func (p *DefaultProvider) AccessTokenStrategy(ctx context.Context, additionalSou
467468
return s
468469
}
469470

470-
func (p *DefaultProvider) TokenHookURL(ctx context.Context) *url.URL {
471-
return p.getProvider(ctx).RequestURIF(KeyTokenHookURL, nil)
471+
type (
472+
Auth struct {
473+
Type string `json:"type"`
474+
Config json.RawMessage `json:"config"`
475+
}
476+
HookConfig struct {
477+
URL string `json:"url"`
478+
Auth *Auth `json:"auth"`
479+
}
480+
)
481+
482+
func (p *DefaultProvider) getHookConfig(ctx context.Context, key string) *HookConfig {
483+
if hookURL := p.getProvider(ctx).RequestURIF(key, nil); hookURL != nil {
484+
return &HookConfig{
485+
URL: hookURL.String(),
486+
}
487+
}
488+
489+
var hookConfig *HookConfig
490+
if err := p.getProvider(ctx).Unmarshal(key, &hookConfig); err != nil {
491+
p.l.WithError(errors.WithStack(err)).
492+
Errorf("Configuration value from key %s could not be decoded.", key)
493+
return nil
494+
}
495+
if hookConfig == nil {
496+
return nil
497+
}
498+
499+
// validate URL by parsing it
500+
u, err := url.ParseRequestURI(hookConfig.URL)
501+
if err != nil {
502+
p.l.WithError(errors.WithStack(err)).
503+
Errorf("Configuration value from key %s could not be decoded.", key)
504+
return nil
505+
}
506+
hookConfig.URL = u.String()
507+
508+
return hookConfig
509+
}
510+
511+
func (p *DefaultProvider) TokenHookConfig(ctx context.Context) *HookConfig {
512+
return p.getHookConfig(ctx, KeyTokenHook)
472513
}
473514

474-
func (p *DefaultProvider) TokenRefreshHookURL(ctx context.Context) *url.URL {
475-
return p.getProvider(ctx).RequestURIF(KeyRefreshTokenHookURL, nil)
515+
func (p *DefaultProvider) TokenRefreshHookConfig(ctx context.Context) *HookConfig {
516+
return p.getHookConfig(ctx, KeyRefreshTokenHook)
476517
}
477518

478519
func (p *DefaultProvider) DbIgnoreUnknownTableColumns() bool {

driver/config/provider_test.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ package config
55

66
import (
77
"context"
8+
"encoding/json"
89
"fmt"
910
"io"
1011
"net/http"
11-
"net/url"
1212
"os"
1313
"strings"
1414
"testing"
@@ -425,17 +425,37 @@ func TestCookieSecure(t *testing.T) {
425425
assert.True(t, c.CookieSecure(ctx))
426426
}
427427

428-
func TestTokenRefreshHookURL(t *testing.T) {
428+
func TestHookConfigs(t *testing.T) {
429429
ctx := context.Background()
430430
l := logrusx.New("", "")
431431
l.Logrus().SetOutput(io.Discard)
432432
c := MustNew(context.Background(), l, configx.SkipValidation())
433433

434-
assert.EqualValues(t, (*url.URL)(nil), c.TokenRefreshHookURL(ctx))
435-
c.MustSet(ctx, KeyRefreshTokenHookURL, "")
436-
assert.EqualValues(t, (*url.URL)(nil), c.TokenRefreshHookURL(ctx))
437-
c.MustSet(ctx, KeyRefreshTokenHookURL, "http://localhost:8080/oauth/token_refresh")
438-
assert.EqualValues(t, "http://localhost:8080/oauth/token_refresh", c.TokenRefreshHookURL(ctx).String())
434+
for key, getFunc := range map[string]func(context.Context) *HookConfig{
435+
KeyRefreshTokenHook: c.TokenRefreshHookConfig,
436+
KeyTokenHook: c.TokenHookConfig,
437+
} {
438+
assert.Nil(t, getFunc(ctx))
439+
c.MustSet(ctx, key, "")
440+
assert.Nil(t, getFunc(ctx))
441+
c.MustSet(ctx, key, "http://localhost:8080/hook")
442+
hc := getFunc(ctx)
443+
require.NotNil(t, hc)
444+
assert.EqualValues(t, "http://localhost:8080/hook", hc.URL)
445+
446+
c.MustSet(ctx, key, map[string]any{
447+
"url": "http://localhost:8080/hook2",
448+
"auth": map[string]any{
449+
"type": "api_key",
450+
"config": json.RawMessage(`{"in":"header","name":"my-header","value":"my-value"}`),
451+
},
452+
})
453+
hc = getFunc(ctx)
454+
require.NotNil(t, hc)
455+
assert.EqualValues(t, "http://localhost:8080/hook2", hc.URL)
456+
assert.EqualValues(t, "api_key", hc.Auth.Type)
457+
assert.JSONEq(t, `{"in":"header","name":"my-header","value":"my-value"}`, string(hc.Auth.Config))
458+
}
439459
}
440460

441461
func TestJWTBearer(t *testing.T) {

go.sum

Lines changed: 0 additions & 37 deletions
Large diffs are not rendered by default.

internal/httpclient/model_token_pagination_response_headers.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

oauth2/oauth2_auth_code_bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func BenchmarkAuthCode(b *testing.B) {
8282
reg := internal.NewRegistrySQLFromURL(b, dsn, true, new(contextx.Default)).WithTracer(tracer)
8383
reg.Config().MustSet(ctx, config.KeyLogLevel, "error")
8484
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque")
85-
reg.Config().MustSet(ctx, config.KeyRefreshTokenHookURL, "")
85+
reg.Config().MustSet(ctx, config.KeyRefreshTokenHook, "")
8686
oauth2Keys, err := jwk.GenerateJWK(ctx, jose.ES256, x.OAuth2JWTKeyName, "sig")
8787
require.NoError(b, err)
8888
oidcKeys, err := jwk.GenerateJWK(ctx, jose.ES256, x.OpenIDConnectKeyName, "sig")

oauth2/oauth2_auth_code_test.go

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
7979
ctx := context.Background()
8080
reg := internal.NewMockedRegistry(t, &contextx.Default{})
8181
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque")
82-
reg.Config().MustSet(ctx, config.KeyRefreshTokenHookURL, "")
82+
reg.Config().MustSet(ctx, config.KeyRefreshTokenHook, "")
8383
publicTS, adminTS := testhelpers.NewOAuth2Server(ctx, t, reg)
8484

8585
publicClient := hydra.NewAPIClient(hydra.NewConfiguration())
@@ -955,6 +955,7 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
955955
return func(t *testing.T) {
956956
hs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
957957
assert.Equal(t, r.Header.Get("Content-Type"), "application/json; charset=UTF-8")
958+
assert.Equal(t, r.Header.Get("Authorization"), "Bearer secret value")
958959

959960
var hookReq hydraoauth2.TokenHookRequest
960961
require.NoError(t, json.NewDecoder(r.Body).Decode(&hookReq))
@@ -981,9 +982,15 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
981982
defer hs.Close()
982983

983984
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
984-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
985+
reg.Config().MustSet(ctx, config.KeyTokenHook, &config.HookConfig{
986+
URL: hs.URL,
987+
Auth: &config.Auth{
988+
Type: "api_key",
989+
Config: json.RawMessage(`{"in": "header", "name": "Authorization", "value": "Bearer secret value"}`),
990+
},
991+
})
985992

986-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
993+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
987994

988995
expectAud := "https://api.ory.sh/"
989996
c, conf := newOAuth2Client(t, reg, testhelpers.NewCallbackURL(t, "callback", testhelpers.HTTPServerNotImplementedHandler))
@@ -1030,9 +1037,9 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
10301037
defer hs.Close()
10311038

10321039
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
1033-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1040+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
10341041

1035-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
1042+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
10361043

10371044
expectAud := "https://api.ory.sh/"
10381045
c, conf := newOAuth2Client(t, reg, testhelpers.NewCallbackURL(t, "callback", testhelpers.HTTPServerNotImplementedHandler))
@@ -1070,9 +1077,9 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
10701077
defer hs.Close()
10711078

10721079
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
1073-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1080+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
10741081

1075-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
1082+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
10761083

10771084
expectAud := "https://api.ory.sh/"
10781085
c, conf := newOAuth2Client(t, reg, testhelpers.NewCallbackURL(t, "callback", testhelpers.HTTPServerNotImplementedHandler))
@@ -1110,9 +1117,9 @@ func TestAuthCodeWithDefaultStrategy(t *testing.T) {
11101117
defer hs.Close()
11111118

11121119
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
1113-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1120+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
11141121

1115-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
1122+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
11161123

11171124
expectAud := "https://api.ory.sh/"
11181125
c, conf := newOAuth2Client(t, reg, testhelpers.NewCallbackURL(t, "callback", testhelpers.HTTPServerNotImplementedHandler))
@@ -1657,11 +1664,11 @@ func TestAuthCodeWithMockStrategy(t *testing.T) {
16571664
defer hs.Close()
16581665

16591666
if hookType == "legacy" {
1660-
conf.MustSet(ctx, config.KeyRefreshTokenHookURL, hs.URL)
1661-
defer conf.MustSet(ctx, config.KeyRefreshTokenHookURL, nil)
1667+
conf.MustSet(ctx, config.KeyRefreshTokenHook, hs.URL)
1668+
defer conf.MustSet(ctx, config.KeyRefreshTokenHook, nil)
16621669
} else {
1663-
conf.MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1664-
defer conf.MustSet(ctx, config.KeyTokenHookURL, nil)
1670+
conf.MustSet(ctx, config.KeyTokenHook, hs.URL)
1671+
defer conf.MustSet(ctx, config.KeyTokenHook, nil)
16651672
}
16661673

16671674
res, err := testRefresh(t, &refreshedToken, ts.URL, false)
@@ -1699,11 +1706,11 @@ func TestAuthCodeWithMockStrategy(t *testing.T) {
16991706
defer hs.Close()
17001707

17011708
if hookType == "legacy" {
1702-
conf.MustSet(ctx, config.KeyRefreshTokenHookURL, hs.URL)
1703-
defer conf.MustSet(ctx, config.KeyRefreshTokenHookURL, nil)
1709+
conf.MustSet(ctx, config.KeyRefreshTokenHook, hs.URL)
1710+
defer conf.MustSet(ctx, config.KeyRefreshTokenHook, nil)
17041711
} else {
1705-
conf.MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1706-
defer conf.MustSet(ctx, config.KeyTokenHookURL, nil)
1712+
conf.MustSet(ctx, config.KeyTokenHook, hs.URL)
1713+
defer conf.MustSet(ctx, config.KeyTokenHook, nil)
17071714
}
17081715

17091716
origAccessTokenClaims := testhelpers.IntrospectToken(t, oauthConfig, refreshedToken.AccessToken, ts)
@@ -1734,11 +1741,11 @@ func TestAuthCodeWithMockStrategy(t *testing.T) {
17341741
defer hs.Close()
17351742

17361743
if hookType == "legacy" {
1737-
conf.MustSet(ctx, config.KeyRefreshTokenHookURL, hs.URL)
1738-
defer conf.MustSet(ctx, config.KeyRefreshTokenHookURL, nil)
1744+
conf.MustSet(ctx, config.KeyRefreshTokenHook, hs.URL)
1745+
defer conf.MustSet(ctx, config.KeyRefreshTokenHook, nil)
17391746
} else {
1740-
conf.MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1741-
defer conf.MustSet(ctx, config.KeyTokenHookURL, nil)
1747+
conf.MustSet(ctx, config.KeyTokenHook, hs.URL)
1748+
defer conf.MustSet(ctx, config.KeyTokenHook, nil)
17421749
}
17431750

17441751
res, err := testRefresh(t, &refreshedToken, ts.URL, false)
@@ -1764,11 +1771,11 @@ func TestAuthCodeWithMockStrategy(t *testing.T) {
17641771
defer hs.Close()
17651772

17661773
if hookType == "legacy" {
1767-
conf.MustSet(ctx, config.KeyRefreshTokenHookURL, hs.URL)
1768-
defer conf.MustSet(ctx, config.KeyRefreshTokenHookURL, nil)
1774+
conf.MustSet(ctx, config.KeyRefreshTokenHook, hs.URL)
1775+
defer conf.MustSet(ctx, config.KeyRefreshTokenHook, nil)
17691776
} else {
1770-
conf.MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1771-
defer conf.MustSet(ctx, config.KeyTokenHookURL, nil)
1777+
conf.MustSet(ctx, config.KeyTokenHook, hs.URL)
1778+
defer conf.MustSet(ctx, config.KeyTokenHook, nil)
17721779
}
17731780

17741781
res, err := testRefresh(t, &refreshedToken, ts.URL, false)
@@ -1794,11 +1801,11 @@ func TestAuthCodeWithMockStrategy(t *testing.T) {
17941801
defer hs.Close()
17951802

17961803
if hookType == "legacy" {
1797-
conf.MustSet(ctx, config.KeyRefreshTokenHookURL, hs.URL)
1798-
defer conf.MustSet(ctx, config.KeyRefreshTokenHookURL, nil)
1804+
conf.MustSet(ctx, config.KeyRefreshTokenHook, hs.URL)
1805+
defer conf.MustSet(ctx, config.KeyRefreshTokenHook, nil)
17991806
} else {
1800-
conf.MustSet(ctx, config.KeyTokenHookURL, hs.URL)
1801-
defer conf.MustSet(ctx, config.KeyTokenHookURL, nil)
1807+
conf.MustSet(ctx, config.KeyTokenHook, hs.URL)
1808+
defer conf.MustSet(ctx, config.KeyTokenHook, nil)
18021809
}
18031810

18041811
res, err := testRefresh(t, &refreshedToken, ts.URL, false)

oauth2/oauth2_client_credentials_test.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func TestClientCredentials(t *testing.T) {
256256

257257
hs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
258258
assert.Equal(t, r.Header.Get("Content-Type"), "application/json; charset=UTF-8")
259+
assert.Equal(t, r.Header.Get("Authorization"), "Bearer secret value")
259260

260261
expectedGrantedScopes := []string{"foobar"}
261262
expectedGrantedAudience := []string{"https://api.ory.sh/"}
@@ -286,9 +287,15 @@ func TestClientCredentials(t *testing.T) {
286287
defer hs.Close()
287288

288289
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
289-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
290+
reg.Config().MustSet(ctx, config.KeyTokenHook, &config.HookConfig{
291+
URL: hs.URL,
292+
Auth: &config.Auth{
293+
Type: "api_key",
294+
Config: json.RawMessage(`{"in": "header", "name": "Authorization", "value": "Bearer secret value"}`),
295+
},
296+
})
290297

291-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
298+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
292299

293300
secret := uuid.New().String()
294301
cl, conf := newCustomClient(t, &hc.Client{
@@ -316,9 +323,9 @@ func TestClientCredentials(t *testing.T) {
316323
defer hs.Close()
317324

318325
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
319-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
326+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
320327

321-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
328+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
322329

323330
_, conf := newClient(t)
324331

@@ -340,9 +347,9 @@ func TestClientCredentials(t *testing.T) {
340347
defer hs.Close()
341348

342349
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
343-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
350+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
344351

345-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
352+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
346353

347354
_, conf := newClient(t)
348355

@@ -364,9 +371,9 @@ func TestClientCredentials(t *testing.T) {
364371
defer hs.Close()
365372

366373
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, strategy)
367-
reg.Config().MustSet(ctx, config.KeyTokenHookURL, hs.URL)
374+
reg.Config().MustSet(ctx, config.KeyTokenHook, hs.URL)
368375

369-
defer reg.Config().MustSet(ctx, config.KeyTokenHookURL, nil)
376+
defer reg.Config().MustSet(ctx, config.KeyTokenHook, nil)
370377

371378
_, conf := newClient(t)
372379

0 commit comments

Comments
 (0)