Skip to content

Commit

Permalink
add ClientAuthenticationStrategy
Browse files Browse the repository at this point in the history
  • Loading branch information
narg95 committed Feb 18, 2021
1 parent c1a6ce3 commit ef7d318
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 1 deletion.
3 changes: 3 additions & 0 deletions access_request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestNewAccessRequest(t *testing.T) {

client := &DefaultClient{}
fosite := &Fosite{Store: store, Hasher: hasher, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}
fosite.WithDefaultClientAuthentication()
for k, c := range []struct {
header http.Header
form url.Values
Expand Down Expand Up @@ -238,6 +239,7 @@ func TestNewAccessRequestWithoutClientAuth(t *testing.T) {
client := &DefaultClient{}
anotherClient := &DefaultClient{ID: "another"}
fosite := &Fosite{Store: store, Hasher: hasher, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}
fosite.WithDefaultClientAuthentication()
for k, c := range []struct {
header http.Header
form url.Values
Expand Down Expand Up @@ -371,6 +373,7 @@ func TestNewAccessRequestWithMixedClientAuth(t *testing.T) {

client := &DefaultClient{}
fosite := &Fosite{Store: store, Hasher: hasher, AudienceMatchingStrategy: DefaultAudienceMatchingStrategy}
fosite.WithDefaultClientAuthentication()
for k, c := range []struct {
header http.Header
form url.Values
Expand Down
12 changes: 11 additions & 1 deletion client_authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import (
jose "gopkg.in/square/go-jose.v2"
)

// ClientAuthenticationStrategy provides a method signature for authenticating a client request
type ClientAuthenticationStrategy func(context.Context, *http.Request, url.Values) (Client, error)

const clientAssertionJWTBearerType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"

func (f *Fosite) findClientPublicJWK(oidcClient OpenIDConnectClient, t *jwt.Token, expectsRSAKey bool) (interface{}, error) {
Expand Down Expand Up @@ -66,7 +69,9 @@ func (f *Fosite) findClientPublicJWK(oidcClient OpenIDConnectClient, t *jwt.Toke
return nil, errorsx.WithStack(ErrInvalidClient.WithHint("The OAuth 2.0 Client has no JSON Web Keys set registered, but they are needed to complete the request."))
}

func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values) (Client, error) {
// DefaultClientAuthentication provides the default fosite's client authentication strategy,
// HTTP Basic Authentication and JWT Bearer
func (f *Fosite) DefaultClientAuthentication(ctx context.Context, r *http.Request, form url.Values) (Client, error) {
if assertionType := form.Get("client_assertion_type"); assertionType == clientAssertionJWTBearerType {
assertion := form.Get("client_assertion")
if len(assertion) == 0 {
Expand Down Expand Up @@ -240,6 +245,11 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u
return client, nil
}

// WithDefaultClientAuthentication use fosite's default client authentication methods
func (f *Fosite) WithDefaultClientAuthentication() {
f.AuthenticateClient = f.DefaultClientAuthentication
}

func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (interface{}, error) {
keys := set.Keys
if len(keys) == 0 {
Expand Down
3 changes: 3 additions & 0 deletions client_authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func TestAuthenticateClient(t *testing.T) {
Hasher: hasher,
TokenURL: "token-url",
}
f.WithDefaultClientAuthentication()

barSecret, err := hasher.Hash(context.TODO(), []byte("bar"))
require.NoError(t, err)
Expand Down Expand Up @@ -489,6 +490,7 @@ func TestAuthenticateClient(t *testing.T) {
store := storage.NewMemoryStore()
store.Clients[tc.client.ID] = tc.client
f.Store = store
f.WithDefaultClientAuthentication()

c, err := f.AuthenticateClient(nil, tc.r, tc.form)
if tc.expectErr != nil {
Expand Down Expand Up @@ -541,6 +543,7 @@ func TestAuthenticateClientTwice(t *testing.T) {
Hasher: hasher,
TokenURL: "token-url",
}
f.WithDefaultClientAuthentication()

formValues := url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
"sub": "bar",
Expand Down
5 changes: 5 additions & 0 deletions compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func Compose(config *Config, storage interface{}, strategy interface{}, hasher f
JWKSFetcherStrategy: config.GetJWKSFetcherStrategy(),
MinParameterEntropy: config.GetMinParameterEntropy(),
UseLegacyErrorFormat: config.UseLegacyErrorFormat,
AuthenticateClient: config.GetClientAuthenticationStrategy(),
}

if f.AuthenticateClient == nil {
f.WithDefaultClientAuthentication()
}

for _, factory := range factories {
Expand Down
13 changes: 13 additions & 0 deletions compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ type Config struct {

// GrantTypeJWTBearerMaxDuration sets the maximum time after JWT issued date, during which the JWT is considered valid.
GrantTypeJWTBearerMaxDuration time.Duration

// ClientAuthenticationStrategy indicates the Strategy to authenticate client requests
ClientAuthenticationStrategy fosite.ClientAuthenticationStrategy
}

// GetScopeStrategy returns the scope strategy to be used. Defaults to glob scope strategy.
Expand Down Expand Up @@ -221,3 +224,13 @@ func (c *Config) GetJWTMaxDuration() time.Duration {
}
return c.GrantTypeJWTBearerMaxDuration
}

// GetClientAuthenticationStrategy returns the configured client authentication strategy.
// Defaults to nil.
func (c *Config) GetClientAuthenticationStrategy() fosite.ClientAuthenticationStrategy {
// It is simpler to provide a nil default, otherwise this config will
// required as input parameter a `fosite.Fosite` instance `f` to return
// f.DefaultClientAuthentication.
// Therefore I preferred to set the default in the compose.Compose method
return c.ClientAuthenticationStrategy
}
3 changes: 3 additions & 0 deletions fosite.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ type Fosite struct {

// FormPostHTMLTemplate sets html template for rendering the authorization response when the request has response_mode=form_post. Defaults to fosite.FormPostDefaultTemplate
FormPostHTMLTemplate *template.Template

// AuthenticateClient provides a extension point to plug a strategy to authenticate clients
AuthenticateClient ClientAuthenticationStrategy
}

const MinParameterEntropy = 8
Expand Down
2 changes: 2 additions & 0 deletions revoke_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func TestNewRevocationRequest(t *testing.T) {

client := &DefaultClient{}
fosite := &Fosite{Store: store, Hasher: hasher}
fosite.WithDefaultClientAuthentication()
for k, c := range []struct {
header http.Header
form url.Values
Expand Down Expand Up @@ -226,6 +227,7 @@ func TestWriteRevocationResponse(t *testing.T) {
defer ctrl.Finish()

fosite := &Fosite{Store: store, Hasher: hasher}
fosite.WithDefaultClientAuthentication()

type args struct {
rw *httptest.ResponseRecorder
Expand Down

0 comments on commit ef7d318

Please sign in to comment.