I am unclear on how to achieve multiple alternate authentication options with precedence -
I can successfully separately implement
- OAuth (Single Sign On)
- API key Authentication (passed as a header)
What I am unclear on - how do I configure it so that if the API key header is present and the API key is valid to process the request; that is if you have a valid API-Key, you do not need the SSO. If no API-Key is presented then you should expect to pass SSO.
The default should be SSO - hence I have the following configured in my Program.cs
builder.Services
.AddAuthentication(
options =>
{
// If an authentication cookie is present, use it to get authentication information
options.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// If authentication is required, and no cookie is present,
// use OAuth to sign in
options.DefaultChallengeScheme = "OAuth";
}
)
.AddCookie(
options =>
{
options.AccessDeniedPath = "/accessdenied";
}
)
.AddOAuth(
"OAuth",
(OAuthOptions options) =>
{
// abstracted out but takes care of claims, etc...
WebApp.Utils.OAuthHelper.Process(options, OAuthConfig);
}
);
But my question is - how do I configure this to say - if API Key Header is present, don't bother with OAuth.
CodePudding user response:
The AuthenticationSchemeOptions
class has ForwardDefaultSelector
that allows any AuthenticationHandler
to forward authentication events to another handler.
So what you can do is make your ApiKeyAuthenticationHandler
the detault and make it forward authentication events to OAuth handler if API Key header is not present in the HTTP headers.
For example: Let's say you implement ApiKeyAuthenticationHandler as Follows:
// API Key options
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public string ApiKeyHeaderName { get; set; } = "X-MY-API-KEY";
// Add more options here if needed
}
// API Key Authentication handler
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
// Inject any additional services needed by your key handler here
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Validation logic here
// Check if the header exists
if (!Context.Request.Headers.TryGetValue(Options.ApiKeyHeaderName, out var headerValues))
{
// Header is not present fail
return AuthenticateResult.Fail("Invalid API key.");
}
// Header is present validate user
var user = await GetApiKeyUserAsync(headerValues.First());
if (user == null)
{
return AuthenticateResult.Fail("Invalid API key.");
}
// API Key is valid, generate a ClaimIdentity and Principal for the authenticated user
var identity = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user)
}, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, null, Scheme.Name));
}
// Key validation logic here and you can map an API key to a user
private async Task<string?> GetApiKeyUserAsync(string key)
{
// Add your implementation here
if (key == "Alice's Key")
{
return "Alice";
}
if (key == "Bob's Key")
{
return "Bob";
}
return null;
}
}
Now in the startup class call AddAuthentication
and make "ApiKey"
the default, and add a Selector function that checks the HTTP Headers for the presence of a key, if the key is present, then return null
and let the Api key handler handle the authentication, otherwiser forward to "OAuth".
builder.Services
.AddAuthentication(
options =>
{
options.DefaultAuthenticateScheme =
"ApiKey";
}
)
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKey", apiKeyOptions =>
{
// This scheme will forward Authentication to OAUTH if API Key header is not present
apiKeyOptions.ForwardDefaultSelector = (context) =>
{
// Check the HTTP Request for the presence of Header
if (!context.Request.Headers.TryGetValue(apiKeyOptions.ApiKeyHeaderName, out var headerValues))
{
// Header is not present, forward to OAuth
return "OAuth"; // "OAuth" is the scheme name you specified when you called "AddOAuth()
}
// Do not forward the authentication
return null;
};
})
// Add other schemes here