Beef leverages and integrates seamlessly with the ASP.NET Core out-of-the-box authentication and authorization capabilities.
The following sections describe the support enabled in each of the underlying solution projects.
The authentication process primarily takes place within the API itself. This capability is generally added within the Startup.cs
leveraging the standard ASP.NET Core capabilities. The minimum requirement is the update of the ExecutionContext
similar to the following within the Configure
method.
For CoreEx the ExecutionContext
plays a key role for housing the user details, namely the Username
(optionally UserId
). For the likes of authorization SetRoles
can also be used. To enable additional capabilities a custom ExecutionContext
can be created (inheriting base) similar to that demonstrated within the Cdr.Banking sample.
Note: in the following example, the Username
is set to emails#oid
claims as the emails
value may not be unique and is mutable, whereas the oid
is unique and immutable. This may be appropriate in your scenario especially where the Username
is used for the likes of auditing.
public void Configure(IApplicationBuilder app)
{
...
app.UseAuthentication();
app.UseExecutionContext((ctx, ec) =>
{
//ec.UserName = ctx.User.Identity?.Name ?? "Anonymous";
//return Task.CompletedTask;
if (ctx.User.Identity is not null && ctx.User.Identity.IsAuthenticated)
{
ec.UserId = ctx.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
if (ec.UserId is null)
throw new AuthenticationException("Token must have an 'oid' (object identifier) claim.");
ec.UserName = $"{ctx.User.FindFirst("emails")?.Value ?? throw new AuthenticationException("Token must have an 'emails' laim.")}#{ec.UserId}";
}
else
ec.UserName = "Anonymous";
return Task.CompletedTask;
});
...
Disclaimer: the above example is purely for illustrative purposes only; it is the responsibility of the developer to implement the appropriate authentication, claims verification, etc. that is applicable to their specific use case.
For the authentication to occur within an API invocation the AuthorizeAttribute
must be specified. The output of this attribute is controlled by the code generation configuration.
The following YAML elements support the webApiAuthorize
attribute, generally specifying either of the two options, being Authorize
or AllowAnonymous
(the value is not validated so any supported value can be specified). The YAML value is inherited from its parent within the hierarchy where not explicitly defined (overridden).
Note: Where no webApiAuthorize
attribute is specified and cannot be inferred via parental inheritence, it will default to AllowAnonymous
.
To support the intra-domain integration testing the bearer token may need to be passed from the test to the API otherwise all requests will fail with an authentication error.
The UnitTextEx.TestSetUp
contains two delegates that can be provided to update the request to include credentials prior to send, these are OnBeforeHttpRequestSendAsync
and OnBeforeHttpRequestMessageSendAsync
depending on requirements. Where the test has been configured for a specified user (see WithUser
), the username is passed as a parameter to the delegate. This should be configured as part of the one-time set up.
// Fixture set up.
TestSetUp.Default.OnBeforeHttpRequestMessageSendAsync = (req, userName, _) =>
{
req.Headers.Add("cdr-user", userName);
return Task.CompletedTask;
};
...
// Test logic with user name specified.
var v = Agent<AccountAgent, AccountCollectionResult>()
.WithUser("jenny")
.ExpectStatusCode(HttpStatusCode.OK)
.Run(a => a.GetAccountsAsync(null)).Value;
Additionally, the authentication can be bypassed altogether, using the likes of the ApiTester.BypassAuthorization
. This will result in the underlying services being configured with a BypassAuthorizationHandler
.
ApiTester.BypassAuthorization();
// Invoke the agent.
Agent<FooAgent, int>()
.ExpectStatusCode(HttpStatusCode.OK)
.Run(a => a.BarAsync(1))
.Assert(1234);