DEV Community

Cover image for Blazor's Authentication
Ruxo Zheng
Ruxo Zheng

Posted on

Blazor's Authentication

Disclaimer: based on my observation, not readings.

Recently, I had to implement Firebase authentication for a Blazor WASM project. To my surprise, I discovered there's no ready-made authentication library for Firebase on .NET. While I found the Firebase Admin library, it wasn't quite what I needed for client-side authentication. So, I ended up building my own solution for .NET.

Fortunately, I didn't have to start from scratch. Firebase provides a JavaScript SDK for authentication, which is quite straightforward to use and integrates well with Blazor WASM.

Blazor Authentication

Blazor handles authorization through a class derived from AuthenticationStateProvider. This class requires you to override a key method: GetAuthenticationStateAsync. You can find more details in the official documentation.

The GetAuthenticationStateAsync method is responsible for returning an instance of AuthenticationState, which encapsulates the current user as a ClaimsPrincipal. The ClaimsPrincipal contains a ClaimsIdentity, which the framework uses to determine whether the user is authenticated by checking the IsAuthenticated property.

By overriding GetAuthenticationStateAsync and returning the appropriate user state, you can manage the application's authentication flow as well as user permissions. Essentially, this means you have full control over how authentication is implemented and when users are considered authenticated or not.

Firebase Authentication Overview

Firebase offers comprehensive documentation for their authentication libraries, which can be found here (JavaScript SDK). Since I was working with Blazor WASM, it made sense to leverage browser modules. Luckily, Firebase also supports this approach, as mentioned in a note on this page.

Image description

Blazor integration

The idea is to use the JS SDK to provide the Firebase authentication, and once we get the authenticated JWT, just hands over to .NET Cookie authentication for storing as .NET cookie, or manually managing the token yourself.

JS interop script

First, we need a Javascript-side interop code to call Firebase authentication. It looks like this:

import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js'
import { getAuth, GoogleAuthProvider, signInWithPopup } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js'

const signin = async (output, config) => {
    const app = initializeApp(config);
    const auth = getAuth(app);

    const provider = new GoogleAuthProvider();

    try {
        const result = await signInWithPopup(auth, provider);
        const credential = GoogleAuthProvider.credentialFromResult(result);
        const user = result.user;
        const info = {
            accessToken: credential.accessToken,
            idToken: user.accessToken
        }
        const encoded = await output.invokeMethodAsync("AfterSignIn", true, info, null)
        // you may use local storage or session storage, etc.
        document.cookie = `after-signin=${encoded}; path=/; Secure; SameSite=Strict`;
    } catch(error) {
        console.error(error);
        output.invokeMethodAsync("AfterSignIn", false, null, "permission-needed")
    }
};

window.firebase = { signin };
Enter fullscreen mode Exit fullscreen mode

In order to use the SDK, you need Firebase configuration, which is passed to the function via config variable. You can find the configuration in Firebase's console, in the application section under your project's settings.

The output variable encapsulates .NET interop class with a method name AfterSignIn. It is an integration for the script and .NET Blazor code. My AfterSignIn method looks like this:

    [JSInvokable]
    public async Task<string?> AfterSignIn(bool success, SignInInfo? info, string? error) {
        // basically you may want to cache the result of login
        afterSignInInfo = success? new AfterSignInInfo(info) : null;
        // return serialized sign-in data as string for storing in the cookie
        return success? JsonSerializer.Serialize(info!) : null;
    }
Enter fullscreen mode Exit fullscreen mode

and the code to invoke the signin function:

async Task Login(){
        var jsRef = DotNetObjectReference.Create(this);
        var fbConfig = JsonSerializer.Deserialize<FirebaseConfig>( Configuration["FirebaseConfig"]!)!;

        await js.InvokeVoidAsync("window.firebase.signin", jsRef, fbConfig);
        if (afterSignInInfo?.IsSuccess ?? false){
            var query = returnUrl is null ? string.Empty : $"?ReturnUrl={HttpUtility.UrlEncode(returnUrl)}";
            navManager.NavigateTo($"/login/success{query}");
        }
}
Enter fullscreen mode Exit fullscreen mode

That's it

The JS code should be generalized enough to be used with any kind of Blazor project (Blazor Server, Blazor Hosted WASM, or just Blazor WASM).

Top comments (0)