How to Decouple Front-End from Back-End with Secure APIs for User Management in Blazor Web App (.NET 8)

Laurent Guigon 306 Reputation points
2024-10-31T14:42:41.7966667+00:00

Hello,

I'm used to creating Blazor Web App projects in .NET 8 with "individual accounts" and interactive render mode set to Auto, with per-page interactivity. The advantage of this setup is that all the account management mechanics are scaffolded, requiring minimal adjustments to meet specific needs.

However, I now need to decouple the front-end from the back-end and use secure APIs, with one of these APIs dedicated to everything handled by Microsoft.AspNetCore.Identity. This API should ensure security across the application—offering all available services (scaffolding, etc.) when integrating account management into the Web App—and also secure other APIs (microservices).

Note: Initially, a single API will serve as the presentation layer for multiple services to avoid dealing with API gateways, as my team is not yet fully familiar with microservices architecture.

My issue is that I have never worked this way before and am not sure how to proceed. I have found many examples on YouTube, but none that closely match what I’m aiming to achieve. The MSDN documentation also hasn’t provided a concrete example for this setup.

Could you guide me on how to approach this? Ideally, I would like my "Users" project to be general enough to be reused in other applications, while also offering all the services, like page scaffolding, similar to what’s available when creating a web app with user accounts.

Thank you in advance.

Microsoft Identity Manager
Microsoft Identity Manager
A family of Microsoft products that manage a user's digital identity using identity synchronization, certificate management, and user provisioning.
709 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,602 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
346 questions
{count} votes

Accepted answer
  1. Tiny Wang-MSFT 2,816 Reputation points Microsoft Vendor
    2024-11-13T02:15:50.38+00:00

    Hi Laurent, based on your description: decouple the front-end from the back-end and use secure APIs + I don't want a cookie; I want a JWT. Also, I want my system to protect my API(s) and require authentication to access the API functions, I think you can use individual accounts + JWT auth in your application just like what Bruce recommended, please allow me to extend here.

    Firstly, you can keep using individual accounts in your front-end blazor application, and I trust you shall have a login method which might use await SignInManager.PasswordSignInAsync(); to validate the user sign-in request. If the user signed in successfully, you need to generate an access token for this user and this is what you need to do extra. The token-generation function shall be similar to codes below. Since the user already signed in, you would get user name and many other information, you can add them into the token claims depending on your business. The code below can be put into one of your backend api project, you can create similar methods in your blazor app as well.

    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    using Microsoft.IdentityModel.Tokens;
    
    private string GenerateAccessToken()
    {
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Name, "username1"),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"));
        var token = new JwtSecurityToken(
            issuer: "Test.com",
            audience: "Test.com",
            expires: DateTime.Now.AddHours(3),
            claims: claims,
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
            );
        var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
        return accessToken;
    }
    

    The token is required to be added into request header Authorization: Bearer {token} to call the secured API.

    using (HttpClient client = new HttpClient())
    {
        // Add necessary headers
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzxxxxliG6-xm54");
        // Send a GET request to the API
        HttpResponseMessage apiResponse = await client.GetAsync("https://localhost:7035/api/hello");
        // Ensure the request was successful
        apiResponse.EnsureSuccessStatusCode();
        // Read the response content as a string
        string responseBody = await apiResponse.Content.ReadAsStringAsync();
    }
    
    

    Next, in the API projects where need to receive access token, you can add codes below in Program.cs, it will help to validate the codes and return 401/403/correct response based on the token in request header. You can change the options based on your business, my codes below are just for test purpose so that I set many options to be false.

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            //var key = new SymmetricSecurityKey(securityService.Key);
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = "Test.com",
                ValidAudience = "Test.com",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("this is my custom Secret key for authentication"))
            };
        });
    

    Certainly, you might want to add some policy for authorization, asp.net core provides poliy-based auth which could help define custom validation logic. You can see this tutorial. You will have to create a TokenHandler and a TokenRequirement,

    //added behind builder.Services.AddAuthentication().AddJwtBearer()
    builder.Services.AddSingleton<IAuthorizationHandler, TokenHandler>();
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("Token", policy =>
            policy.Requirements.Add(new TokenRequirement()));
    });
    
    
    public class TokenRequirement : IAuthorizationRequirement
    {
        //public TokenRequirement(int minimumAge) =>
        //MinimumAge = minimumAge;
        //public int MinimumAge { get; }
    }
    
    using Microsoft.AspNetCore.Authorization;
    using System.IdentityModel.Tokens.Jwt;
    using System.Text;
    public class TokenHandler : AuthorizationHandler<TokenRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
        {
            if (context != null)
            {
                var httpContext = (DefaultHttpContext)context.Resource;
                if (httpContext != null)
                {
                    string jwtToken = httpContext.Request.Headers["Authorization"].ToString();
    				var handler = new JwtSecurityTokenHandler();
    				var res = jwtToken.Substring(7);
    				JwtSecurityToken token = handler.ReadJwtToken(res);
                    var claims = token.Claims;
                    if (token.Claims.Where(c => c.Type == "role" && c.Value == @"XXXX Authentication1").Count() > 0)
                        //context.Fail();
                        context.Succeed(requirement);
                    else
                    {
                        //context.Fail();
                        context.Succeed(requirement);
                        //throw new NullReferenceException("my custom message");
                    }
                }
                else
                    //context.Fail();
                    context.Succeed(requirement);
            }
            return Task.CompletedTask;
        }
    }
    

    Then you can add [Authorize(Policy = "Token")] in front of controller class or action to apply this policy.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Tiny

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.