dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.25k stars 9.95k forks source link

[OpenId Connect] Silent Authentication in iframe redirects parent window on error #28028

Closed keggster101020 closed 3 years ago

keggster101020 commented 3 years ago

Hi,

I'm trying to do silent auth/refresh for users on my web page by having a hidden iframe call a SignIn api on my server. I'm using AADv2 for the auth server and whenever authentication can be refreshed successfully everything works as expected.

However, when there is an error in the silent auth flow (e.g.: interaction_required errors) something is issuing a redirect to /signin-oidc in the parent window. That is, the redirect is breaking out of the iframe; which is obviously undesired behavior.

I've been able to reproduce this issue with a brand new ASP NET CORE project so I can rule out something in my services code that is causing the issue.

Any help would be appreciated. Thanks

Expected Behavior

The iframe returns the challenge call and then either redirects to /index with authenticated user state or the iframe redirects to /error if the silent challenge fails on the auth server

Actual Behavior

When the silent login call fails, the parent window is redirected to /singin-oidc with an error message. Not the iframe

Auth Setup

services.AddOptions<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme)
                .Bind(configuration)
                .Configure<IOptionsMonitor<CertificateOptions>, IHttpClientFactory>((options, certificateOptionsMonitor, httpClientFactory) =>
                {
                    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.Backchannel = httpClientFactory.CreateClient(OpenIdConnectDefaults.AuthenticationScheme);

                    options.Scope.Add("offline_access");

                    // request authorization code with ID token
                    options.ResponseType = OpenIdConnectResponseType.CodeIdToken;

                    options.SaveTokens = true;

                                        options.Events.OnRedirectToIdentityProvider = context =>
                    {

                           context.ProtocolMessage.Prompt = "none";

                        return Task.CompletedTask;
                    };

                                        //register other callbacks including auth code received for client assertion

services.AddAuthentication(options =>
                {
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                })
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, _ => { });

Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.ConfigureAuthentication(Configuration.GetSection("AAD")); //extension method from above with aad config values for auth
            services.AddRouting();
            services.AddMvc()
                .AddRazorPagesOptions(options => { })
                .AddViewLocalization();
        }

// 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();
            }
            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.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();
            app.UseAuthentication();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
            });
        }

Auth Controller

[Route("[controller]/[action]")]
    public class AuthenticationController : Controller
    {
        [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult SignIn(
            [FromQuery(Name = "ru")] string returnUrl = null)
        {
            var authenticationProperties = CreateAuthenticationProperties(returnUrl);

            authenticationProperties.SetString(nameof(OpenIdConnectMessage.Prompt), "none");

            return Challenge(authenticationProperties);
        }
    }

IFrame Setup

var authUrl = Url.Action(nameof(AuthenticationController.SignIn), "Authentication", new { ru = Url.PageLink("Index") });
<iframe src="@authUrl"></iframe>

Project: ASP.NET Core v3.1 Nuget Packages:

Microsoft.AspNetCore.Authentication.OpenIdConnect Version="3.1.10"
Microsoft.Identity.Client Version="4.22.0"
Microsoft.IdentityModel.JsonWebTokens Version="6.8.0"
Microsoft.IdentityModel.Protocols.OpenIdConnect Version="6.8.0"
blowdart commented 3 years ago

As an aside to your problem, with Safari adding more privacy protection, and Chrome and Edge following, it's likely frame based logins aren't going to work for much longer,

For AAD login you may want to consider moving to Identity.Web which is the new Azure library, and is what .NET 5 uses (and we'll be updating the templates for 3.1 in Feb to use)

keggster101020 commented 3 years ago

Closing this issue as it is an issue with the identity server and not the framework .

Identity server bug: https://github.com/AzureAD/microsoft-identity-web/issues/778