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 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 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.
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 };
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;
}
and the code to invoke the signin
function:
async Task Login(){
var jsRef = DotNetObjectReference.Create(this);
var fbConfig = 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}");
}
}
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).
Source link
lol