Lesson 6: Implementing Firebase Login & JWT Authentication in .NET

0
107

Lesson 6: Implementing Firebase Authentication & JWT in .NET

Meta Description

Learn how to implement Firebase authentication using JWT in a .NET backend. This guide covers user registration, login, and secure authentication while handling dynamic Firebase configurations for multiple projects.


What is JWT (JSON Web Token)?

JWT (JSON Web Token) is a secure way to transmit user authentication data between a client and a server. It consists of three parts:

  1. Header โ†’ Defines the type (JWT) and algorithm (HS256).
  2. Payload โ†’ Contains claims such as user ID and expiration time.
  3. Signature โ†’ Ensures the tokenโ€™s integrity.

Example JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Step 1: Updating the FirebaseRequest.cs Model

The FirebaseRequest model holds the request data needed for authentication.

Modify FirebaseRequest.cs

namespace CGZAPI.Models
{
    public class FirebaseRequest
    {
        public object FirebaseJson { get; set; } // Supports both string and object
        public string Email { get; set; }
        public string Password { get; set; }
        public string Token { get; set; } // Used for JWT authentication
        public bool IsEncrypted { get; set; } // Defines if Firebase JSON is encrypted
        public string FirebaseProjectId { get; set; } // Allows dynamic Firebase authentication
        public string ApiKey { get; set; } // ๐Ÿ”น Add this field for authentication

        public string GetFirebaseJsonAsString()
        {
            return FirebaseJson is string jsonString
                ? jsonString
                : Newtonsoft.Json.JsonConvert.SerializeObject(FirebaseJson);
        }
    }
}

Explanation:

  • FirebaseJson: Supports both JSON strings and objects.
  • ApiKey: Required for Firebase authentication requests.
  • GetFirebaseJsonAsString: Ensures proper conversion of JSON to a string.

Step 2: Implement Firebase Authentication in FirebaseManager.cs

Modify FirebaseManager.cs

using FirebaseAdmin;
using FirebaseAdmin.Auth;
using Google.Apis.Auth.OAuth2;
using System.Collections.Generic;

namespace CGZAPI.Services
{
    public static class FirebaseManager
    {
        private static Dictionary<string, FirebaseApp> firebaseApps = new Dictionary<string, FirebaseApp>();

        public static FirebaseApp InitializeFirebase(string firebaseJson, bool isEncrypted)
        {
            string decodedJson = isEncrypted ? EncryptionHelper.Decrypt(firebaseJson) : firebaseJson;
            if (!firebaseApps.ContainsKey(decodedJson))
            {
                var credentials = GoogleCredential.FromJson(decodedJson);
                var appOptions = new AppOptions()
                {
                    Credential = credentials
                };
                firebaseApps[decodedJson] = FirebaseApp.Create(appOptions, decodedJson);
            }
            return firebaseApps[decodedJson];
        }
    }
}

Explanation:

  • InitializeFirebase: Initializes Firebase dynamically for each project.
  • firebaseApps Dictionary: Stores Firebase instances to avoid duplicate initializations.

Step 3: Implement Authentication Endpoints in AuthController.cs

Modify AuthController.cs

using Microsoft.AspNetCore.Mvc;
using FirebaseAdmin.Auth;
using CGZAPI.Services;
using CGZAPI.Models;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json;
using System.Text.Json;

namespace CGZAPI.Controllers
{
    [Route("api/auth")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        [HttpPost("register")]
        public async Task<IActionResult> RegisterUser([FromBody] FirebaseRequest request)
        {
            try
            {
                string firebaseJsonString = request.GetFirebaseJsonAsString();
                var firebaseApp = FirebaseManager.InitializeFirebase(firebaseJsonString, request.IsEncrypted);
                var auth = FirebaseAuth.GetAuth(firebaseApp);
                var user = await auth.CreateUserAsync(new UserRecordArgs
                {
                    Email = request.Email,
                    Password = request.Password,
                });
                return Ok(new { message = "User registered successfully", userId = user.Uid });
            }
            catch (Exception ex)
            {
                return StatusCode(500, new { error = "Internal Server Error", details = ex.Message });
            }
        }

        [HttpPost("login")]
        public async Task<IActionResult> LoginUser([FromBody] FirebaseRequest request)
        {
            var firebaseJsonString = request.GetFirebaseJsonAsString();
            var firebaseApp = FirebaseManager.InitializeFirebase(firebaseJsonString, request.IsEncrypted);

            using var httpClient = new HttpClient();
            var signInUrl = $"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={request.ApiKey}";

            var signInData = new
            {
                email = request.Email,
                password = request.Password,
                returnSecureToken = true
            };

            var response = await httpClient.PostAsJsonAsync(signInUrl, signInData);

            if (!response.IsSuccessStatusCode)
            {
                return BadRequest("Invalid credentials");
            }

            var jsonResponse = await response.Content.ReadAsStringAsync();
            var result = System.Text.Json.JsonDocument.Parse(jsonResponse).RootElement;

            if (!result.TryGetProperty("idToken", out var idToken) || !result.TryGetProperty("localId", out var localId))
            {
                return BadRequest("Invalid Firebase response.");
            }

            return Ok(new
            {
                message = "Login successful",
                userId = localId.GetString(),
                token = idToken.GetString()
            });
        }
    }
}

Explanation:

  • register โ†’ Registers a new user in Firebase.
  • login โ†’ Uses Firebase REST API with an API Key for authentication.

Step 4: Secure Routes with JWT in Program.cs

Modify Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var firebaseProjectId = context.Request.Headers["FirebaseProjectId"].ToString();
                if (!string.IsNullOrEmpty(firebaseProjectId))
                {
                    options.Authority = $"https://securetoken.google.com/{firebaseProjectId}";
                    options.Audience = firebaseProjectId;
                }
                return Task.CompletedTask;
            }
        };
    });
builder.Services.AddAuthorization();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

Explanation:

  • Enables dynamic Firebase authentication using FirebaseProjectId.
  • Uses ApiKey for login authentication requests.