Faking authentication and authorization
bUnit comes with test-specific implementations of Blazor's authentication and authorization types, making it easy to test components that use Blazor's <AuthorizeView>
, <CascadingAuthenticationState>
and <AuthorizeRouteView>
components, as well as the AuthenticationStateProvider
type.
The test implementation of Blazor's authentication and authorization can be put into the following states:
- Authenticating
- Unauthenticated and unauthorized
- Authenticated and unauthorized
- Authenticated and authorized
- Authenticated and authorized with one or more roles, claims, and/or policies
bUnit's authentication and authorization implementation is easily available by calling AddTestAuthorization()
on a test context. This adds the necessary services to the Services
collection and the CascadingAuthenticationState
component to the root render tree. The method returns an instance of the TestAuthorizationContext type that allows you to control the authentication and authorization state for a test.
Note
If your test class inherits directly from bUnit's TestContext then you need to call the AddTestAuthorization()
method on this
, since AddTestAuthorization()
is an extension method, otherwise it wont be available. E.g.: this.AddTestAuthorization()
.
The following sections show how to set each of these states in a test.
Setting authenticating, authenticated and authorized states
The examples in the following sections will use the <UserInfo>
component listed below. This uses an injected AuthenticationStateProvider
service and <CascadingAuthenticationState>
and <AuthorizeView>
components to show the user name when a user is authenticated. It also shows the authorization state when the authenticated user is authorized.
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
@if (isAuthenticated)
{
<h1>Welcome @userName</h1>
}
@if (!isAuthenticated)
{
<h1>Please log in!</h1>
}
<AuthorizeView>
<Authorized>
<p>State: Authorized</p>
</Authorized>
<Authorizing>
<p>State: Authorizing</p>
</Authorizing>
<NotAuthorized>
<p>State: Not authorized</p>
</NotAuthorized>
</AuthorizeView>
@code
{
bool isAuthenticated = false;
string userName;
protected override async Task OnParametersSetAsync()
{
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
isAuthenticated = state.User.Identity.IsAuthenticated;
userName = state.User.Identity.Name;
}
}
The following subsections demonstrate how to set the <UserInfo>
into all three authentication and authorization states.
Unauthenticated and unauthorized state
To set the state to unauthenticated and unauthorized, do the following:
// Arrange
this.AddTestAuthorization();
// Act
var cut = RenderComponent<UserInfo>();
// Assert
cut.MarkupMatches(@"<h1>Please log in!</h1>
<p>State: Not authorized</p>");
The highlighted line shows how AddTestAuthorization()
is used to add the test-specific implementation of Blazor's authentication and authorization types to the Services
collection, which makes the authentication state available to other services as well as components used throughout the test that require it.
After calling AddTestAuthorization()
, the default authentication state is unauthenticated and unauthorized.
Authenticating and authorizing state
To set the state to authenticating and authorizing, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorizing();
// Act
var cut = RenderComponent<UserInfo>();
// Assert
cut.MarkupMatches(@"<h1>Please log in!</h1>
<p>State: Authorizing</p>");
After calling AddTestAuthorization()
, the returned TestAuthorizationContext is used to set the authenticating and authorizing state through the SetAuthorizing() method.
Authenticated and unauthorized state
To set the state to authenticated and unauthorized, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER", AuthorizationState.Unauthorized);
// Act
var cut = RenderComponent<UserInfo>();
// Assert
cut.MarkupMatches(@"<h1>Welcome TEST USER</h1>
<p>State: Not authorized</p>");
After calling AddTestAuthorization()
, the returned TestAuthorizationContext is used to set the authenticated and unauthorized state through the SetAuthorized(string, AuthorizationState) method.
Authenticated and authorized state
To set the state to authenticated and authorized, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
// Act
var cut = RenderComponent<UserInfo>();
// Assert
cut.MarkupMatches(@"<h1>Welcome TEST USER</h1>
<p>State: Authorized</p>");
After calling AddTestAuthorization()
, the returned TestAuthorizationContext is used to set the authenticated and authorized state through the SetAuthorized(string, AuthorizationState) method.
Note that the second parameter, AuthorizationState
, is optional, and defaults to AuthorizationState.Authorized
if not specified.
Setting authorization details
The following section will show how to specify roles and/or policies in a test.
The examples will use the <UserRights>
component listed below. It uses the <AuthorizeView>
component to include different content based on the roles, claims, or policies specified in each test.
@using Microsoft.AspNetCore.Components.Authorization
@using System.Security.Claims
@using System.Globalization
<AuthorizeView>
<h1>Hi @context.User.Identity.Name, you have these claims and rights:</h1>
</AuthorizeView>
<ul>
<AuthorizeView>
@foreach (var claim in @context.User.FindAll(x => x.Type != ClaimTypes.Name && x.Type != ClaimTypes.Role))
{
<li>@GetClaimName(claim): @claim.Value</li>
}
</AuthorizeView>
<AuthorizeView Roles="superuser">
<li>You have the role SUPER USER</li>
</AuthorizeView>
<AuthorizeView Roles="admin">
<li>You have the role ADMIN</li>
</AuthorizeView>
<AuthorizeView Policy="content-editor">
<li>You are a CONTENT EDITOR</li>
</AuthorizeView>
<AuthorizeView>
@if(context.User.Identity?.AuthenticationType == "custom-auth-type")
{
<li>You have the authentication type CUSTOM AUTH TYPE</li>
}
</AuthorizeView>
</ul>
@code
{
private static string GetClaimName(Claim claim)
{
var claimType = new Uri(claim.Type);
var name = claimType.Segments.Last();
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(name);
}
}
Roles
To specify one or more roles for the authenticated and authorized user, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetRoles("superuser");
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>You have the role SUPER USER</li>
</ul>");
The highlighted line shows how the SetRoles(params string[]) method is used to specify a single role. To specify multiple roles, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetRoles("admin", "superuser");
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>You have the role SUPER USER</li>
<li>You have the role ADMIN</li>
</ul>");
Policies
To specify one or more policies for the authenticated and authorized user, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetPolicies("content-editor");
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>You are a CONTENT EDITOR</li>
</ul>");
}
The highlighted line shows how the SetPolicies(params string[]) method is used to specify one policy. To specify multiple policies, do the following:
// Assert
Claims
To specify one or more claims for the authenticated and authorized user, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetClaims(
new Claim(ClaimTypes.Email, "[email protected]"),
new Claim(ClaimTypes.DateOfBirth, "01-01-1970")
);
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>Emailaddress: [email protected]</li>
<li>Dateofbirth: 01-01-1970</li>
</ul>");
The highlighted line shows how the SetClaims(params Claim[]) method is used to pass two instances of the Claim
type.
Example of passing both roles, claims, and policies
Let’s try to combine all the possibilities shown in the previous examples into one. The following example specifies two roles, one claim, and one policy for the authenticated and authorized user:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetRoles("admin", "superuser");
authContext.SetPolicies("content-editor");
authContext.SetClaims(new Claim(ClaimTypes.Email, "[email protected]"));
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>Emailaddress: [email protected]</li>
<li>You have the role SUPER USER</li>
<li>You have the role ADMIN</li>
<li>You are a CONTENT EDITOR</li>
</ul>");
With this example done, all auth-related test scenarios should be covered. If you find that one is missing, please let us know in the bUnit discussion forum.
Authentication types
To specify a authentication type for the authenticated and authorized user, do the following:
// Arrange
var authContext = this.AddTestAuthorization();
authContext.SetAuthorized("TEST USER");
authContext.SetAuthenticationType("custom-auth-type");
// Act
var cut = RenderComponent<UserRights>();
// Assert
cut.MarkupMatches(@"<h1>Hi TEST USER, you have these claims and rights:</h1>
<ul>
<li>You have the authentication type CUSTOM AUTH TYPE</li>
</ul>");
The highlighted line shows how the SetAuthenticationType(string) method is used to change the Identity.AuthenticationType
of the user.