Ethan Schofer
Ethan Schofer

Reputation: 1848

Using multiple authorization providers - Cookie auth and Open ID connect

Im trying to use multiple auth providers - a very simple cookie auth, and open id connect. Most of my users will use the simple cookie auth. But, a few users are already on the open id connect system and I want them to be able to connect. I can can get one or the other of these to work, but not both. Adding openid connect overrides the other cookie system. How can I get these to play nicely with each other. An acceptable solution would be for the Open ID connect to not redirect to the log in screen, and these would users would need to manually go log in.

Here is my Startup class:

public class Startup
{
    public Startup(IConfiguration configuration) => this.Configuration = configuration;

    public IConfiguration Configuration { get; }

    private CultureInfo[] supportedCultures;
    private CultureInfo[] SupportedCultures => this.supportedCultures ??= new[] { new CultureInfo("en-US") };

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRouting(options =>
        {
            options.LowercaseUrls = true;
            options.AppendTrailingSlash = false;
            options.LowercaseQueryStrings = true;
        });

        // Add MVC with feature folders
        services.AddMvc(opt => opt.Filters
            .Add(typeof(DbContextTransactionFilter)))
            .AddFeatureFolders()
            .AddViewLocalization()
            .AddDataAnnotationsLocalization();

        // Add HTTP Context to DI Container
        services.AddHttpContextAccessor();

        // Add localization
        services.AddLocalization();
        services.Configure<RequestLocalizationOptions>(options =>
        {
            // Just supporting English right now
            options.DefaultRequestCulture = new RequestCulture("en-US", "en-US");

            // You must explicitly state which cultures your application supports.
            // These are the cultures the app supports for formatting 
            // numbers, dates, etc.
            options.SupportedCultures = this.SupportedCultures;

            // These are the cultures the app supports for UI strings, 
            // i.e. we have localized resources for.
            options.SupportedUICultures = this.SupportedCultures;
        });

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

        services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie(options =>
            {
                options.AccessDeniedPath = "/request-access";
                options.ExpireTimeSpan = TimeSpan.FromDays(14);
                options.LoginPath = "/request-access";
                options.LogoutPath = "/request-access";
                options.Cookie.Name = "AMGAuthCookie";
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.ClientId = "MyClientID";
                options.Authority = "https://myidentityserver.com/";
                options.RequireHttpsMetadata = false;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.ResponseType = "code token";

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("sitecore.profile");
                options.Scope.Add("offline_access");
                options.Scope.Add("sitecore.profile.api");

                options.SaveTokens = true;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = JwtClaimTypes.Name,
                    RoleClaimType = JwtClaimTypes.Role,
                };
            }); 
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        var pageOptions = new DeveloperExceptionPageOptions { SourceCodeLineCount = 10 };
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage(pageOptions);
        }
        else
        {
            app.UseStatusCodePagesWithReExecute("/error/{0}");
            // 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.UseAuthentication();
        app.UseAuthorization();

        var cookiePolicyOptions = new CookiePolicyOptions
        {
            MinimumSameSitePolicy = SameSiteMode.Strict,
        };
        app.UseCookiePolicy(cookiePolicyOptions);

        app.UseRequestLocalization(new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture("en-US"),
            SupportedCultures = this.SupportedCultures,
            SupportedUICultures = this.SupportedCultures
        });

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

Upvotes: 1

Views: 1328

Answers (2)

Dasith Wijes
Dasith Wijes

Reputation: 1358

Use the ForwardDefaultSelector as described here https://leastprivilege.com/2018/06/14/mixing-ui-and-api-endpoints-in-asp-net-core-2-1-aka-dynamic-scheme-selection/


       services
            .AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = "dynamic";
            })
            .AddPolicyScheme("dynamic", "Cookie or JWT", options =>
            {
                options.ForwardDefaultSelector = context =>
                {
                    var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
                    var isApiPath = context.Request.Path.StartsWithSegments("/api");
                    if (authHeader?.StartsWith("Bearer ") || isApiPath)
                    {
                        return JwtBearerDefaults.AuthenticationScheme;
                    }
                    return CookieAuthenticationDefaults.AuthenticationScheme;
                };
            })
            .AddJwtBearer(o =>
            {
                o.Authority = Configuration["JWT:Authentication:Authority"];
                o.Audience = Configuration["JWT:Authentication:ClientId"];
                o.SaveToken = true;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

            ...

            }

Upvotes: 3

Ethan Schofer
Ethan Schofer

Reputation: 1848

I solved this problem by leaving my Startup.cs as above, but then changing how I authorize my controllers. When I authorize, I set a specific scheme. Cookie Scheme gets me the cookie log in, OpenIdConnect Scheme gets me the OpenIdConnect log in. I will probably need to juggle resources a little as I build out the app, but this seems to do the trick. Luckily, there is a business logic difference between users who will login via cookie vs users who will log in via OpenIDConnect.

[Authorize(AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)]

Upvotes: 0

Related Questions