Introduction

BitzArt.Blazor.Auth is a Blazor library that provides tearless authentication and authorization capabilities for your Blazor applications. Thanks to Blazor.Cookies, it works with all Blazor United render modes:

Blazor RendermodeSupport
Static SSR✔️
Interactive Server✔️
Interactive WebAssembly✔️
Interactive Auto✔️

Getting Started

Refer to the Getting Started section to learn how to use this library in your Blazor applications.

Getting Started

Installation

Add this nuget package to your Server project:

dotnet add package BitzArt.Blazor.Auth.Server

Add this nuget package to your Client project:

dotnet add package BitzArt.Blazor.Auth.Client

Configuration

Call this method in both your Server Program.cs and Client Program.cs:

builder.AddBlazorAuth();

Usage

You can now use Blazor authorization capabilities (such as @attribute [Authorize], AuthorizeView, AuthorizeRouteView) as you normally would.

What's next?

Refer to the Authentication section to learn how to customize your authentication logic.

Authentication

💡 You can always refer to the sample project for additional guidance.

In order to implement User authentication with this library, you need to implement the IAuthenticationService in your Blazor Server project and specify it when calling the AddBlazorAuth method in your Blazor Server Program.cs file.

ℹ️ IAuthenticationService will be available in both your Server and Client projects, but you should only implement it in your Server project. The Client project will use the Server implementation out-of-the-box by calling the Server's API.

Implementation

Sign-In Payload

Create a class for your Sign-In payload. This class will be used to pass the user's credentials to the IAuthenticationService implementation.

📜 This class needs to be serializable, so it can be passed between the Client and Server projects over http.

// Example Sign-In payload
public class SignInPayload
{
    public string Email { get; set; }
    public string Password { get; set; }
}

Sign-Up Payload

You don't need to do this if you don't want to implement the Sign-Up functionality.

Create a class for your Sign-Up payload. This class will be used to pass the user's credentials to the IAuthenticationService implementation.

📜 This class needs to be serializable, so it can be passed between the Client and Server projects over http.

// Example Sign-Up payload
public class SignUpPayload
{
    public string Email { get; set; }
    public string Password { get; set; }
}

Authentication Service

You can inherit from the base ServerSideAuthenticationService class in order to simplify the implementation of the IAuthenticationService in your Blazor Server project.

// Example Authentication Service
public class SampleServerSideAuthenticationService
    : ServerSideAuthenticationService<SignInPayload, SignUpPayload>
{
    protected override Task<AuthenticationResult> GetSignInResultAsync(SignInPayload signInPayload)
    {
        var jwtPair = CreateSampleJwtPair(); // Replace this with your actual authentication logic

        var authResult = AuthenticationResult.Success(jwtPair);

        return Task.FromResult(authResult);
    }

    protected override Task<AuthenticationResult> GetSignUpResultAsync(SignUpPayload signUpPayload)
    {
        var jwtPair = CreateSampleJwtPair(); // Replace this with your actual authentication logic

        var authResult = AuthenticationResult.Success(jwtPair);

        return Task.FromResult(authResult);
    }

    public override Task<AuthenticationResult> RefreshJwtPairAsync(string refreshToken)
    {
        var jwtPair = CreateSampleJwtPair(); // Replace this with your actual authentication logic

        var authResult = AuthenticationResult.Success(jwtPair);

        return Task.FromResult(authResult);
    }

    private JwtPair CreateSampleJwtPair()
    {
        return new JwtPair
        {
            AccessToken = "my-access-token",
            RefreshToken = "my-refresh-token"
        };
    }
}

Register the Authentication Service

Specify your IAuthenticationService implementation when calling the AddBlazorAuth method in your Blazor Server Program.cs file.

// Program.cs
builder.AddBlazorAuth<SampleServerSideAuthenticationService>();

Add the authentication endpoints in your Blazor Server Program.cs. This is required in order to allow the Client project to call the Server's API and use the server-side IAuthenticationService implementation.

// Program.cs
app.MapAuthEndpoints();

User Service

You can now use the IUserService in your Blazor Pages to Sign-In, Sign-Up, and Refresh the JWT Pair. The IUserService will call your IAuthenticationService implementation to perform these actions. Upon receiving a response, the IUserService will then update the User's state. For additional guidance, see sample flows in Use Cases section.

ℹ️ You can also inject and use the IAuthenticationService directly in your Blazor application, it will process the Sign-In, Sign-Up, and Refresh Token requests without updating the User's state. You can inject the IAuthenticationService in the wasm part of your app. It will call the Server's WebAPI to perform the authentication action and return the same response as if the action was performed on the server. Keep in mind that in this case a serialization/deserialization process will take place in order to pass the data between the Client and Server projects.

Token duration

You can specify the duration of your access token and refresh token whenever you are providing your JwtPair to Blazor.Auth. The duration of the access token should normally be short, while the refresh token should normally be long-lived.

return new JwtPair
    {
        AccessToken = "my-access-token"
        RefreshToken = "my-refresh-token"
        AccessTokenExpiresAt = DateTimeOffset.UtcNow.AddMinutes(15),
        RefreshTokenExpiresAt = DateTimeOffset.UtcNow.AddDays(7)
    }

⚠️ Not providing an expiration date for the tokens will result in them being session-scoped. This means that the tokens will expire when the browser tab closes.

Sign out

You can sign the user out by calling the SignOutAsync method of the IUserService. This will clear the user's JwtPair.

await UserService.SignOutAsync();
NavigationManager.NavigateTo("/", true);

Use Cases

While your end soultion code regarding authentication remains the same, the way Blazor.Auth behaves under the hood may vary depending on your current render mode.

The following segments will guide you through the most common use-cases such as Sign-In, Sign-Up and Sign-Out and show you how to implement them in your Blazor application.

More detailed diagrams are organized according to different Blazor interactivity types:

Refresh Token

On every GetAuthenticationStateAsync call to AuthenticationStateProvider, it will check if the current access token is expired. If it is, the user's JWT Pair will automatically be refreshed.

For a more in-depth overview of this process, refer to the Architecture section.

Back to Use Cases

Static SSR

The following sequence diagrams illustrate the use cases for Blazor.Auth in a Blazor application that is currently using Static SSR render mode.

For implementing these use-cases, an approach utilizing form submission is used. For more information on how Blazor handles form submission in Static SSR, refer to Blazor Documentation.

Sign-In

sequenceDiagram

actor user as User
participant page as SignIn Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userService as IUserService (Server)
end
participant authService as IAuthenticationService (Server)

user ->>+ page: Submit Sign-In Form

page ->>+ userService: SignInAsync(signInPayload)
note over user,userService: Call IUserService's `SignInAsync` method when handling sign-in form submission.

userService ->>+ authService: SignInAsync(signInPayload)
authService ->> authService: Your server-side sign-in logic

authService -->>- userService: AuthenticationResult

userService -->> userService: Update authentication cookies
userService -->>- page: AuthenticationResult

page -->>- user:  Form submission HTTP Response
note over user,userService: Updated cookies will be attached to the HTTP response <br/> which will result in them being automalically stored in the user's browser.

Sign-Up

sequenceDiagram

actor user as User
participant page as SignUp Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userService as IUserService (Server)
end
participant authService as IAuthenticationService (Server)

user ->>+ page: Submit Sign-Up Form

page ->>+ userService: SignUpAsync(signUpPayload)
note over user,userService: Call IUserService's `SignUpAsync` method when handling sign-up form submission.

userService ->>+ authService: SignUpAsync(signUpPayload)
authService ->> authService: Your server-side sign-up logic

authService -->>- userService: AuthenticationResult

userService -->> userService: Update authentication cookies
userService -->>- page: AuthenticationResult

page -->>- user:  Form submission HTTP Response
note over user,userService: Updated cookies will be attached to the HTTP response <br/> which will result in them being automalically stored in the user's browser.

Sign-Out

sequenceDiagram

actor user as User
participant page as Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userService as IUserService (Server)
end

user ->>+ page: Submit Sign-Out Form

page ->>+ userService: SignOutAsync()
note over user,userService: Call IUserService's `SignOutAsync` method when handling sign-out form submission.

userService -->> userService: Remove authentication cookies

userService -->>- page: 

page -->>- user:  Form submission HTTP Response
note over user,userService: Blazor.Auth will remove the user's authentication cookies <br/> by marking them as expired in the HTTP response.

Back to Use Cases

Interactive WebAssembly

The following sequence diagrams illustrate the use cases for Blazor.Auth in a Blazor application that is currently using Interactive WebAssembly render mode:

Sign-In

sequenceDiagram

actor user as User
participant page as SignIn Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userServiceClient as IUserService (Client)
participant userServiceServer as IUserService (Server)
end

participant authService as IAuthenticationService (Server)

user ->>+ page: Submit Sign-In Form
page ->>+ userServiceClient: SignInAsync(signInPayload)
note over page,userServiceClient: Resolve IUserService from DI<br/> and call it's `SignInAsync` method.
userServiceClient ->>+ userServiceServer: HTTP request to Sign-In endpoint
note over userServiceClient,userServiceServer: Client-side implementation <br/> will make an HTTP request to the server, <br/> no manual action is required.

userServiceServer ->>+ authService: SignInAsync(signInPayload)
authService ->> authService: Your server-side sign-in logic

authService -->>- userServiceServer: AuthenticationResult
userServiceServer -->> userServiceClient: AuthenticationResult
note over userServiceServer,userServiceClient: Server-side implementation will <br/> return AuthenticationResult to the client#59; <br/><br/> Updated cookies will be attached <br/> to the HTTP response <br/> which will result in them being <br/> automalically stored in the user's browser.

userServiceClient -->>- page: AuthenticationResult
page -->>- user: Continue, <br/> e.g. redirect to home page, <br/> or other custom logic

note over user,userServiceClient: Upon receiving AuthenticationResult from IUserService, <br/> do a page refresh in order to refresh the User's AuthenticationState<br/><br/> Example: NavigationManager.NavigateTo("/", true).

Sign-Up

sequenceDiagram

actor user as User
participant page as SignUp Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userServiceClient as IUserService (Client)
participant userServiceServer as IUserService (Server)
end

participant authService as IAuthenticationService (Server)

user ->>+ page: Submit Sign-Up Form
page ->>+ userServiceClient: SignUpAsync(signUpPayload)
note over page,userServiceClient: Resolve IUserService from DI<br/> and call it's `SignUpAsync` method.
userServiceClient ->>+ userServiceServer: HTTP request to Sign-Up endpoint
note over userServiceClient,userServiceServer: Client-side implementation <br/> will make an HTTP request to the server, <br/> no manual action is required.

userServiceServer ->>+ authService: SignUpAsync(signUpPayload)
authService ->> authService: Your server-side sign-up logic

authService -->>- userServiceServer: AuthenticationResult
userServiceServer -->> userServiceClient: AuthenticationResult
note over userServiceServer,userServiceClient: Server-side implementation will <br/> return AuthenticationResult to the client#59; <br/><br/> Updated cookies will be attached <br/> to the HTTP response <br/> which will result in them being <br/> automalically stored in the user's browser.

userServiceClient -->>- page: AuthenticationResult
page -->>- user: Continue, <br/> e.g. redirect to home page, <br/> or other custom logic

note over user,userServiceClient: Upon receiving AuthenticationResult from IUserService, <br/> do a page refresh in order to refresh the User's AuthenticationState<br/><br/> Example: NavigationManager.NavigateTo("/", true).

Sign-Out

sequenceDiagram

actor user as User
participant page as Page

box rgba(101, 63, 232, 0.5) Blazor.Auth
participant userServiceClient as IUserService (Client)
participant userServiceServer as IUserService (Server)
end

user ->>+ page: Initiate Sign-Out
page ->>+ userServiceClient: SignOutAsync()
note over page,userServiceClient: Resolve IUserService from DI<br/> and call it's `SignOutAsync` method.
userServiceClient ->>+ userServiceServer: HTTP request to Sign-Out endpoint
note over userServiceClient,userServiceServer: Client-side implementation will <br/> make an HTTP request to the server, <br/> no manual action is required.

userServiceServer -->> userServiceClient: HTTP OK
note over userServiceServer,userServiceClient: Blazor.Auth will remove <br/> the user's authentication cookies <br/> by marking them as expired <br/> in the HTTP response.

userServiceClient -->>- page: #32;
page -->>- user: Continue, <br/> e.g. redirect to home page, <br/> or other custom logic

note over user,userServiceClient: Upon sign-out process completion, <br/> do a page refresh in order to refresh the User's AuthenticationState<br/><br/> Example: NavigationManager.NavigateTo("/", true).

Back to Use Cases

Interactive Server

The following sequence diagrams illustrate the use cases for Blazor.Auth in a Blazor application that is currently using Interactive Server render mode:

Sign-In

🚧 This use-case is currently not supported for this render mode. Check back later.

Sign-Up

🚧 This use-case is currently not supported for this render mode. Check back later.

Sign-Out

🚧 This use-case is currently not supported for this render mode. Check back later.

Architecture

The package works by setting and reading Cookies in User's Browser. The Cookies are used to store the JWT Pair (Access Token and Refresh Token). The Access Token is used to authenticate the User in the Server, and the Refresh Token is used to get a new Access Token when the current one expires.

AuthenticationStateProvider implementations

InteractiveWebAssembly

sequenceDiagram

participant client as Blazor Client
participant provider as InteractiveWasmAuthenticationStateProvider
participant server as Blazor Server

client ->>+ provider: GetAuthenticationStateAsync

provider ->>+ server: Http request: /auth-state

server ->> server: if AccessToken expired: Refresh JwtPair

server ->> server: Retrieve Claims from cookies

server -->>- provider: Claims
note over server,provider: Http response updates browser cookies

provider ->> provider: Creates AuthenticationState <br/> from provided claims

provider -->>- client: AuthenticationState

InteractiveServer

sequenceDiagram

participant client as Blazor Client
participant server as Blazor Server
participant provider as InteractiveServerAuthenticationStateProvider

server ->>+ provider: GetAuthenticationStateAsync
provider ->> provider: Generate unique request identifier
provider ->>+ server: Subscribe
note over server,provider: Subscribe to an HTTP request from client using unique identifier
server -->>- provider: 

provider ->>+ client: RequestClientSideHttpRequestAsync
note over client,provider: Request Blazor Client to make an HTTP Request to Blazor Server via JS Interop
client -->> provider: 

note over client: Un-awaited JS promise stays<br/>after JSInterop call is completed
provider -->> provider: Wait until OnClientSideHttpRequest<br/>Invokation is complete
client -)+ server: HTTP Request
deactivate client
server ->>+ provider: OnClientSideHttpRequest (id: uniqueIdentifier, request: HttpRequest)
provider ->> provider: if AccessToken expired: Refresh JwtPair
provider -->>- server: UpdatedCookies
server ->> server: Remove Subscription (id: uniqueIdentifier)
server ->> server: Update cookies in HttpResponse
server -->>- client: OK
provider ->> provider: Generate AuthenticationState
provider -->>- server: AuthenticationState