iframe for authentication in Blazor WebAssembly?

Paul Marangoni 126 Reputation points
2021-05-12T02:40:58.587+00:00

I've just begun to experiment with Blazor/WebAssembly, and I notice that several iframes are generated (visible in the rendered html), within which there is a bunch of cookie hashing for use with Identity.

Is this really how you guys are implementing authentication out of the box? With iframes?

If so, I am very disappointed.

95745-screenshot-2021-05-11-194150.jpg

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,577 questions
{count} vote

4 answers

Sort by: Most helpful
  1. AgaveJoe 28,291 Reputation points
    2021-06-01T10:52:19.36+00:00

    What exactly is the problem?

    I believe you selected the Identity Server template and that's just how Identity Server works. See the docs. Identity Server uses an iframe to detect if use's session has ended on the server. If you are not happy with Identity Server's implementation then you can always roll your own, use the MSAL library or use a different authentication server and API.

    Microsoft Identity Platform.

    1 person found this answer helpful.

  2. AgaveJoe 28,291 Reputation points
    2021-06-02T00:36:16.883+00:00

    But if the server is in the same domain, it could easily use XMLHttpRequest. In my case, I am not authenticating against a different domain. This is why I was surprised by the iframe. Bottom line is that there really isn't enough documentation available documenting this, or showing examples of different approaches. It shouldn't be this convoluted.

    There is no shortage of information on this subject. The most common approach for securing an SPA is OAuth/OIDC which is what you have now. Keep in mind, all you had to do to get up and running is select a few options in Visual Studio. This whole rant started because you saw an iframe and made the assumption that the technology was from 1997 which is not true.

    Is there a starter template for Blazor WebAssembly that authenticates users and uses something other than Identity Server? ASP.NET Identity?

    Yes, and I provided links above. The problem is you created a mental picture of how you think security in Blazor WASM should work. Unfortunately, your assumption is incorrect. Blazor like any SPA is downloaded and runs in the browser. In other words, the whole application is downloaded before the user authenticates. Typically REST services (Web API), where the app get its data, are secured. The client (Blazor application) must get a token, usually from a token server, to access secured data from the REST service. The REST service knows and trusts the token server.

    Anyway, go through the docs and learn OAuth/OIDC. You'll get to the Aha moment...

    1 person found this answer helpful.

  3. AgaveJoe 28,291 Reputation points
    2021-06-02T11:43:52.057+00:00

    True, but there is a ton of conflicting information on it, and Microsoft glosses over the Authentication/Authorization most of the time. Just look at the other comments above asking why I'm using an iframe.

    You are making assumptions based on opinion and prior observations not facts.

    I have no interest in OAuth. I only want to authorize user accounts that are under my control. I know how SPAs work (or rather, suck). I understand using JSON Web Tokens. I have plenty of experience with .NET Identity and the old Membership Providers of the past too.

    The old Membership Provider and Identity uses cookie authentication to secure ASP.NET applications. Given your responses, I'm guessing you want to host Blazor with Web API, Razor Pages, and/or MVC. I'm also guessing you to want to use cookie authentication rather than following standard modern security practices.

    1. Create a new Blazor WASM project.
    2. Select the Hosted option.
    3. Do not select an authentication option since you are going rouge.

    The template creates three projects; client, server, and shared. The server project hosts Web API and the Blazor application. From this point you can scaffold Identity in the server project by following the docs. I built a sample project using cookie authentication without Identity to make the project as light weight as possible.

    The Startup file

    public class Startup  
        {  
            public Startup(IConfiguration configuration)  
            {  
                Configuration = configuration;  
            }  
      
            public IConfiguration Configuration { get; }  
      
            // This method gets called by the runtime. Use this method to add services to the container.  
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940  
            public void ConfigureServices(IServiceCollection services)  
            {  
      
                services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();  
      
                services.AddControllersWithViews();  
                services.AddRazorPages();  
            }  
      
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
            {  
                if (env.IsDevelopment())  
                {  
                    app.UseDeveloperExceptionPage();  
                    app.UseWebAssemblyDebugging();  
                }  
                else  
                {  
                    app.UseExceptionHandler("/Error");  
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.  
                    app.UseHsts();  
                }  
      
                app.UseHttpsRedirection();  
                app.UseBlazorFrameworkFiles();  
                app.UseStaticFiles();  
      
                app.UseRouting();  
      
                app.UseAuthentication();  
                app.UseAuthorization();  
      
                app.UseEndpoints(endpoints =>  
                {  
                    endpoints.MapRazorPages();  
                    endpoints.MapControllers();  
                    endpoints.MapFallbackToFile("index.html");  
                });  
            }  
        }  
    

    The controller

        [Route("[controller]")]  
        public class LoginController : Controller  
        {  
      
            public IActionResult Index()  
            {  
                return View();  
            }  
      
            [HttpPost]  
            public async Task<IActionResult> IndexAsync(LoginModel model)  
            {  
                var claims = new List<Claim>  
                {  
                    new Claim(ClaimTypes.Name, model.Username),  
                    new Claim(ClaimTypes.Role, "Administrator"),  
                };  
      
                var claimsIdentity = new ClaimsIdentity(  
                    claims, CookieAuthenticationDefaults.AuthenticationScheme);  
      
                var authProperties = new AuthenticationProperties  
                {  
                    //AllowRefresh = <bool>,  
                    // Refreshing the authentication session should be allowed.  
      
                    //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),  
                    // The time at which the authentication ticket expires. A   
                    // value set here overrides the ExpireTimeSpan option of   
                    // CookieAuthenticationOptions set with AddCookie.  
      
                    //IsPersistent = true,  
                    // Whether the authentication session is persisted across   
                    // multiple requests. When used with cookies, controls  
                    // whether the cookie's lifetime is absolute (matching the  
                    // lifetime of the authentication ticket) or session-based.  
      
                    //IssuedUtc = <DateTimeOffset>,  
                    // The time at which the authentication ticket was issued.  
      
                    //RedirectUri = <string>  
                    // The full path or absolute URI to be used as an http   
                    // redirect response value.  
                };  
      
                await HttpContext.SignInAsync(  
                    CookieAuthenticationDefaults.AuthenticationScheme,  
                    new ClaimsPrincipal(claimsIdentity),  
                    authProperties);  
      
                return Redirect("~/");  
            }  
        }  
    

    The code will create an authentication cookie and you can secure MVC endpoints with the cookie. I'm not sure how well this will work with Web API as Web API is stateless and clients do not expect to send a cookie.

        [Authorize]  
        [ApiController]  
        [Route("[controller]")]  
        public class WeatherForecastController : ControllerBase  
        {  
            private static readonly string[] Summaries = new[]  
            {  
                "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"  
            };  
      
            private readonly ILogger<WeatherForecastController> _logger;  
      
            public WeatherForecastController(ILogger<WeatherForecastController> logger)  
            {  
                _logger = logger;  
            }  
      
            [HttpGet]  
            public IEnumerable<WeatherForecast> Get()  
            {  
                var rng = new Random();  
                return Enumerable.Range(1, 5).Select(index => new WeatherForecast  
                {  
                    Date = DateTime.Now.AddDays(index),  
                    TemperatureC = rng.Next(-20, 55),  
                    Summary = Summaries[rng.Next(Summaries.Length)]  
                })  
                .ToArray();  
            }  
        }  
    

    You can go with a JWT rather than cookie authentication. Configure JWT bearer tokens in the startup. Create a Web API endpoint that accepts user credentials and returns the JWT to Blazor app. It's the same idea as the login action above except the action returns a JWT. You'll need to write code to persist the JWT and send the JWT bearer token to each secured Web API endpoints.

    Unfortunately, you have not defined the client(s) or what you are securing. I still recommend that you learn OAuth/OIDC so you understand what you're giving up by creating a custom security solution.

    1 person found this answer helpful.

  4. AgaveJoe 28,291 Reputation points
    2021-06-06T11:49:11.853+00:00

    I will never create a serious application of any use that authenticates and authorizes anyone via a service that is not under my control.

    Yeah, you're making assumptions again. Centralized account management is very helpful. You don't have to worry about several applications that manage internal user accounts. Did the developer write secure code? What if there is a security update? It's painful to update and deploy every code base that handles internal user accounts. I prefer one service that handles account management. That's all it does. Unfortunately, I still support old apps that manage their own user accounts. It's the reality of the SDLC and old apps.

    Keep in mind, many organizations have a very strict network security policy. For example, publish facing applications cannot access a database directly, the apps must go through an application server. Only the application server can get to the database. This is a very typically network security setup and to a certain extent facilities the need for centralized authentication/authorization. Anyway, that's what I meant when referring to 3-tier applications. Your responses indicate you do not have this situation.

    If you've worked with enough organizations you will realize that most (if not all) don't do things properly. The current spate of ransomware attacks is proof of that.

    Of course, you should do your best to secure your applications to not leak user information that affects the rest of us.

    1 person found this answer helpful.
    0 comments No comments

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.