How to merge multiple authentication schema in one (Custom Authentication)

1.4k Views Asked by At

I have multiple authentication in my .NET Core API using add multiple Jwt bearer, assume I have Schema1, Schema2 and Schema3.

I'm also using Ocelot to manage requests. In Ocelot config for each route I could declare one and only one authentication type (schema name). However in some scenarios I need to have multiple authentication support for each route. (I need to authenticate request with either of Schema1, Schema2 or Schema3).

But as Ocelot just let me add one schema name, I have to merge these 3 schemas to 1 schema.

So the basic question is: How could I define one authentication schema (SchemaX) which authenticate by Schema1 or Schema2 or Schema3? Any idea?

2

There are 2 best solutions below

0
On

You can try to create custom AuthorizeFilter to allow multiple authentication schema like here or write a custom middleware that check url route from http context and manually calls AuthenticateAsync() and creates a ClaimsPrincipal containing all the identities you need like :

app.UseAuthentication();
app.Use(async (context, next) =>
{
  var principal = new ClaimsPrincipal();

  var result1 = await context.AuthenticateAsync("MyScheme");
  if (result1?.Principal != null)
  {
      principal.AddIdentities(result1.Principal.Identities);
  }

  var result2 = await context.AuthenticateAsync("MyScheme2");
  if (result2?.Principal != null)
  {
      principal.AddIdentities(result2.Principal.Identities);
  }

  context.User = principal;

  await next();
});

Of course you can also move the authentication logic from three schemes into one scheme .

1
On

It was simple, I answer it here if somebody looking for:

Basically just need to define a custom AuthenticationHandler:

public class DynamicAuthenticationOptions : AuthenticationSchemeOptions
{
}
public class DynamicAuthenticationHandler : AuthenticationHandler<DynamicAuthenticationOptions>
{

    public DynamicAuthenticationHandler(
        IOptionsMonitor<DynamicAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock
    )
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.ContainsKey("Authorization"))
            return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));

        string authorizationHeader = Request.Headers["Authorization"];
        if (string.IsNullOrEmpty(authorizationHeader))
            return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));

        if (!authorizationHeader.StartsWith("bearer", StringComparison.OrdinalIgnoreCase))
            return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));

        var token = authorizationHeader.Substring("bearer".Length).Trim();

        IEnumerable<Claim> claims;

        foreach (var validMergedSchema in validMergedSchemas)
        {
            if (IsTokenValid(token, validMergedSchema, out claims))
            {
                var identity = new ClaimsIdentity(claims, Scheme.Name);
                var principal = new System.Security.Principal.GenericPrincipal(identity, null);
                var ticket = new AuthenticationTicket(principal, Scheme.Name);
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
        }

        return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));

    }

    private bool IsTokenValid(string tokenStr, string schema, out IEnumerable<Claim> claims)
    {
        claims = null;

        switch (schema.ToLower())
        {
            case "schema1":
                return IsTokenValidBySchema1(tokenStr, out claims);

            case "schema2":
                return IsTokenValidBySchema2(tokenStr, out claims);

            case "schema3":
                return IsTokenValidBySchema3(tokenStr, out claims);

            default:
                return false;
        }

    }
}