question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Aspnet External Cookie not set for Sign On with Microsoft Post 4.72 SameSite Changes on Asp.net MVC

See original GitHub issue

Apologies for being a bit verbose, but want to try and be specific. I am having trouble with .AspNet.ExternalCookie after the same site changes of Nov 2019 and Azure update of Jan 2020

Setup:

  • .net Framework 4.72 Post samesite changes
  • Azure/IIS on windows 10
  • asp.net MVC

Issue: Sign on with Microsoft was broken with the changes to support SameSite on Azure. We were able to bandaid fix by applying the “roll back” technique of aspnet:SuppressSameSiteNone.

Symptoms: With the suppress same site flag post logging in with Microsoft the .AspNet.ExternalCookie is set but without samesite:none. Without the flag the cookie is never set (or at least in a way I can see it)

What I have tried

this sounds like the problem on https://github.com/aspnet/AspNetKatana/issues/324 which was closed, without a real resolution - the requester said when he re-installed the the path the bug returned.

my code, apology for a lot of it, I tried to rip out as much as I could that was extraneous.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using Abc.Auth.Identity.DBContext;
using Abc.Auth.Identity.Helpers;
using Abc.Auth.Identity.Models;
using Abc.Auth.Providers;
using Abc.Constants.AppSettings;
using System;
using System.Threading.Tasks;

namespace Abc.Web
{
    public class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }


        //Copied from https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Security.Cookies/Provider/DefaultBehavior.cs
        private static bool IsAjaxRequest(IOwinRequest request)
        {
            IReadableStringCollection query = request.Query;
            if (query != null)
            {
                if (query["X-Requested-With"] == "XMLHttpRequest")
                {
                    return true;
                }
            }

            IHeaderDictionary headers = request.Headers;
            if (headers != null)
            {
                if (headers["X-Requested-With"] == "XMLHttpRequest")
                {
                    return true;
                }
            }
            return false;
        }


        public virtual void Configuration(IAppBuilder app)
        {

            ConfigureAuth(app);
        }
        public void ConfigureAuth(IAppBuilder app)
        {

            // Configure the db context, user manager and role manager to use a single instance per request
            app.CreatePerOwinContext(MyIdentityDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie

            var cao = new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/account/logon"),
                LogoutPath = new PathString("/account/logout"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                       validateInterval: TimeSpan.FromMinutes(20),
                       regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie),
                       getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))),

                    // Same logic as https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Security.Cookies/Provider/DefaultBehavior.cs
                    // from the "internal static readonly Action<CookieApplyRedirectContext> ApplyRedirect" method, except if its an ajax request we just return the code 
                    // instead of the "X-Responded-JSON with 200 response" nonsense
                    OnApplyRedirect = (ctx =>
                    {
                        if (!IsAjaxRequest(ctx.Request))
                        {
                            ctx.Response.Redirect(ctx.RedirectUri);
                        }
                    }),

                    /* I changed this part */
                    OnException = (context =>
                    {
                        throw context.Exception;
                    })
                },

                // Potential fix for OWIN authentication issues:
                //http://katanaproject.codeplex.com/wikipage?title=System.Web%20response%20cookie%20integration%20issues&referringTitle=Documentation
                //https://katanaproject.codeplex.com/workitem/197
                //https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser
                CookieManager = new SameSiteCookieManager(new SystemWebCookieManager())

            };

            app.UseCookieAuthentication(cao);
            app.UseExternalSignInCookie();

            Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions office365 = CreateOffice365Options();

            app.UseOpenIdConnectAuthentication(office365);

        }

        private OpenIdConnectAuthenticationOptions CreateOffice365Options()
        {
            var office365 = new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions();
            office365.ClientId = Statics.Office365ClientId; ;
            office365.Caption = "Office 365";

            office365.Authority = Statics.Office365AuthorityBaseUri;

            office365.CookieManager = new SameSiteCookieManager(new SystemWebCookieManager());

            office365.TokenValidationParameters = new TokenValidationParameters
            {
                // instead of using the default validation (validating against a single issuer value, as we do in line of business apps (single tenant apps)),
                // we turn off validation
                //
                // NOTE:
                // * In a multitenant scenario you can never validate against a fixed issuer string, as every tenant will send a different one.
                // * If you don’t care about validating tenants, as is the case for apps giving access to 1st party resources, you just turn off validation.
                // * If you do care about validating tenants, think of the case in which your app sells access to premium content and you want to limit access only to the tenant that paid a fee,
                //       you still need to turn off the default validation but you do need to add logic that compares the incoming issuer to a list of tenants that paid you,
                //       and block access if that’s not the case.
                // * Refer to the following sample for a custom validation logic: https://github.com/AzureADSamples/WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet

                ValidateIssuer = false
            };
            office365.Notifications = new OpenIdConnectAuthenticationNotifications()
            {
                // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.

                RedirectToIdentityProvider = (context) =>
                {
                    // This ensures that the address used for sign in and sign out is picked up dynamically from the request
                    // this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
                    // Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
                    string appBaseUrl = context.Request.Scheme + Uri.SchemeDelimiter + context.Request.Host + context.Request.PathBase;
                    context.ProtocolMessage.RedirectUri = appBaseUrl + "/account/office365";
                    context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;

                    return Task.FromResult(0);
                },

                AuthenticationFailed = (context) =>
                {
                    // Suppress the exception if you don't want to see the error

                    //Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception($"DEBUG: O365 auth failed: {context.Exception.Message}"));
                    Elmah.ErrorSignal.FromCurrentContext().Raise(context.Exception);
                    context.HandleResponse();
                    return Task.FromResult(0);
                }
            };

            return office365;
        }

    }



    public class SameSiteCookieManager : ICookieManager
    {
        private readonly ICookieManager _innerManager;

        public SameSiteCookieManager() : this(new CookieManager())
        {
        }

        public SameSiteCookieManager(ICookieManager innerManager)
        {
            _innerManager = innerManager;
        }

        public void AppendResponseCookie(IOwinContext context, string key, string value,
                                         CookieOptions options)
        {
            CheckSameSite(context, options);
            _innerManager.AppendResponseCookie(context, key, value, options);
        }

        public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
        {
            CheckSameSite(context, options);
            _innerManager.DeleteCookie(context, key, options);
        }

        public string GetRequestCookie(IOwinContext context, string key)
        {
            return _innerManager.GetRequestCookie(context, key);
        }

        private void CheckSameSite(IOwinContext context, CookieOptions options)
        {
            if (options.SameSite == SameSiteMode.None && DisallowsSameSiteNone(context))
            {
                options.SameSite = null;
            }
        }

        public static bool DisallowsSameSiteNone(IOwinContext context)
        {
            return false;
        }
    }

}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:24 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
Tratchercommented, Feb 5, 2020

Does the LinkedInAuthenticationOptions allow changing CookieManager? I can’t see that option in LinkedInAuthenticationOptions

That’s not one we maintain. Looks like it came from here? It hasn’t been updated in a while, someone might have to go add that option.

0reactions
AaronShermanSbtcommented, Feb 15, 2020

the changes above resolved my problem.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Work with SameSite cookies in ASP.NET
Learn how to use to SameSite cookies in ASP.NET. ... NET 4.7.2 and 4.8; History and changes; Known Issues; Supporting older browsers ...
Read more >
Upcoming SameSite Cookie Changes in ASP.NET and ...
The change adds a new SameSite value, “None”, and changes the default behavior to “Lax”. This breaks OpenIdConnect logins, and potentially other ...
Read more >
SameSite cookie sample for ASP.NET 4.7.2 C# MVC
In the sample we wire up the event to a static method which checks whether the browser supports the new sameSite changes, and...
Read more >
Work with SameSite cookies in ASP.NET Core
Redefines the behavior of SameSiteMode.None to emit SameSite=None · Adds a new value SameSiteMode.Unspecified to omit the SameSite attribute.
Read more >
SameSite cookie sample for ASP.NET 4.7.2 VB MVC
In the sample we wire up the event to a static method which checks whether the browser supports the new sameSite changes, and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found