fosite icon indicating copy to clipboard operation
fosite copied to clipboard

Add support for custom client secret validation

Open hilariocoelho opened this issue 2 years ago • 0 comments

Preflight checklist

Describe your problem

I would like to override the following method that validates the client secret:

func (f *Fosite) checkClientSecret(ctx context.Context, client Client, clientSecret []byte) error {
	var err error
	err = f.Config.GetSecretsHasher(ctx).Compare(ctx, client.GetHashedSecret(), clientSecret)
	if err == nil {
		return nil
	}
	cc, ok := client.(ClientWithSecretRotation)
	if !ok {
		return err
	}
	for _, hash := range cc.GetRotatedHashes() {
		err = f.Config.GetSecretsHasher(ctx).Compare(ctx, hash, clientSecret)
		if err == nil {
			return nil
		}
	}

	return err
}

My use case scenario would be with two services: A - User facing application that implements fosite B - Backend application, not internet exposed, with strict security policies that is responsible for managing clients (create, update, delete clients and storing the secrets)

  1. Service A would receive an OAuth request that requires
  2. Service A verifies if Client ID exists and is not Public
  3. If it is private then it calls service B to verify client's secret

The following diagram better illustrates the infrastructure:

image

Describe your ideal solution

Create a new interface ClientSecretValidationStrategyProvider that may be custom implemented using the Configurator:

// ClientSecretValidationStrategy provides a method signature for validating a client secret
type ClientSecretValidationStrategy func(context.Context, Client, []byte) error

// ClientSecretValidationStrategyProvider returns the provider for configuring the client secret validation strategy.
type ClientSecretValidationStrategyProvider interface {
	// GetClientSecretValidationStrategy returns the client secret validation strategy.
	GetClientSecretValidationStrategy(ctx context.Context) ClientSecretValidationStrategy
}

type Configurator interface {
	...
        ClientSecretValidationStrategyProvider
        ...
}

func (f *Fosite) checkClientSecret(ctx context.Context, client Client, clientSecret []byte) error {
	if s := f.Config.GetClientSecretValidationStrategy(ctx); s != nil {
		return s(ctx, client, clientSecret)
	}
	return f.DefaultCheckClientSecret(ctx, client, clientSecret)
}

// DefaultCheckClientSecret provides the fosite's default client secret validation strategy.
func (f *Fosite) DefaultCheckClientSecret(ctx context.Context, client Client, clientSecret []byte) error {
	var err error
	err = f.Config.GetSecretsHasher(ctx).Compare(ctx, client.GetHashedSecret(), clientSecret)
	if err == nil {
		return nil
	}
	cc, ok := client.(ClientWithSecretRotation)
	if !ok {
		return err
	}
	for _, hash := range cc.GetRotatedHashes() {
		err = f.Config.GetSecretsHasher(ctx).Compare(ctx, hash, clientSecret)
		if err == nil {
			return nil
		}
	}

	return err
}

Workarounds or alternatives

Alternative 1: Overriding all methods that implements checkClientSecrets:

  • DefaultClientAuthenticationStrategy -> 170 lines method where I would only need to change the last 3 lines
  • NewIntrospectionRequest -> 68 lines method where I would only need to change 3 lines

This is far from ideal since it would be hard to maintain and keep-up with Fosite updates (even possibly security updates on these two specific methods).

Alternative 2: Implement a custom Hasher overriding the method Compare

Overriding Hasher interface would be more like an hack since it is intended to be used for a implementing hashing algorithms and not different secrets comparison flows.

Version

v0.42.2

Additional Context

No response

hilariocoelho avatar Aug 12 '22 15:08 hilariocoelho