Léster
Léster

Reputation: 1279

How to fix error 404 when logging out on an ASP.NET Core MVC app against Azure AD?

I'm integrating an ASP.NET Core MVC 3.0 app to Azure AD for authentication and authorization adn everything works well, but when I try to sign out, once login.microsoftonline.com signs me out, it redirects to my app and then the following error springs up:

No webpage was found for the web address:

https://localhost:5002/Account/SignOut?page=%2FAccount%2FSignedOut

The path I use to invoke the signout process is /AzureAD/Account/SignOut.

Contents of appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "[OMITTED]",
    "TenantId": "[OMITTED]",
    "ClientId": "[OMITTED]",
    "CallbackPath": "/signin-oidc"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

These are the contents of my Startup.cs class:

using System.Collections.Generic;
using System.Globalization;
using MySite.WebSite.Helpers;
using MySite.WebSite.Models.Validators;
using MySite.WebSite.Models.ViewModels;
using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.AzureAD.UI;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;

namespace MySite.WebSite
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services
                .AddAuthentication(AzureADDefaults.AuthenticationScheme)
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

            services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, options =>
            {
                options.AccessDeniedPath = "/Home/AccessDenied";
                options.LogoutPath = "/";
            });

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {
                options.Authority += "/v2.0/";
                options.TokenValidationParameters.ValidateIssuer = false;
            });

            services.AddLocalization(options => options.ResourcesPath = "Resources");
            services
                .AddControllersWithViews(options => options.Filters.Add(GetAuthorizeFilter()))
                .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
                .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
                .AddDataAnnotationsLocalization()
                .AddFluentValidation();

            services.AddTransient<IValidator<ContactIndexViewModel>, ContactIndexViewModelValidator>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseRequestLocalization(GetLocalizationOptions());
            app.UseStaticFiles(GetStaticFileOptions());
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }

        private RequestLocalizationOptions GetLocalizationOptions()
        {
            var cookie_request_culture_provider = new CookieRequestCultureProvider
            {
                CookieName = "UserCulture"
            };
            var providers = new List<IRequestCultureProvider>()
            {
                cookie_request_culture_provider,
                new AcceptLanguageHeaderRequestCultureProvider()
            };

            var result = new RequestLocalizationOptions
            {
                RequestCultureProviders = providers,
                SupportedCultures = Cultures.SupportedCultures,
                SupportedUICultures = Cultures.SupportedCultures,
                DefaultRequestCulture = new RequestCulture(Cultures.DefaultCulture)
            };
            return result;
        }

        private StaticFileOptions GetStaticFileOptions()
        {
            var result = new StaticFileOptions
            {
                ServeUnknownFileTypes = true,
                DefaultContentType = "text/plain"
            };
            return result;
        }

        private AuthorizeFilter GetAuthorizeFilter()
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            var result = new AuthorizeFilter(policy);
            return result;
        }
    }
}

Upvotes: 5

Views: 6286

Answers (5)

jaybro
jaybro

Reputation: 1573

We have a .NET 6 Blazor app that would not handle the MicrosoftIdentity/Account controller calls which resulted in 404 Not Found issues. After trying all of the fixes here, the line (in addition to the other required middleware extension methods) that got our app to handle those routes was:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
    endpoints.MapControllers(); // <-- This line made the difference for us
});

Upvotes: 2

erradi mourad
erradi mourad

Reputation: 279

you have to change asp-area="AzureAD" value to asp-area="MicrosoftIdentity" in the _LoginPartial.cshtml file

Upvotes: 3

Vince G.
Vince G.

Reputation: 101

I spent half a day chasing this down, realizing it got broken when we migrated to .NET Core 3.0.

We also migrated to Microsoft Identity because the AzureADB2C UI was marked as obsolete. So, in ConfigureServices, we added:

services.AddRazorPages()
            .AddMicrosoftIdentityUI();

...and as Paul said, in Configure we added:

endpoints.MapRazorPages();

This solved the problem for us. We got the hint from creating a brand new MVC Web App with Microsoft Identity authentication from template and compared the differences in our ConfigureServices / Configure methods.

Upvotes: 6

Paul Devenney
Paul Devenney

Reputation: 1339

In (at least) the latest versions you should add endpoints.MapRazorPages(); in the Configure() method of Startup. This handles the route for you.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    endpoints.MapRazorPages();//ADD THIS LINE
});

Upvotes: 15

L&#233;ster
L&#233;ster

Reputation: 1279

Turns out this is a known issue in Microsoft.AspNetCore.Authentication.AzureAD.UI; that package implements the Azure AD authentication/authorization flow in ASP.NET Core, and part of that is an embedded AccountController (area AzureAD) that takes the signin - signout processes out of your shoulders. Problem is, the SignOut action hardcodes a redirect to /Account/SignOut?page=%2FAccount%2FSignedOut once the signout process is complete, and there's the problem.

I managed to solve it by implementing a small AccountController (without an area) and adding a single SignOut action that handles the redirect from Microsoft.AspNetCore.Authentication.AzureAD.UI's AccountController:

[AllowAnonymous]
public class AccountController : Controller
{
    [HttpGet]
    public IActionResult SignOut(string page)
    {
        return RedirectToAction("Index", "Home");
    }
}

Upvotes: 5

Related Questions