0

I've got two DBs, one for business entities and one for Identity. I'm also using external (Azure Ad) login to authenticate to Identity. All of this worked when I had it all in one DB and one DbContext. However, as soon as I split it out, the issue I run into is that after logging in, on subsequent requests User.Identity.IsAuthenticated is false (and thus User.Identity.Name is null, as well as Role/Claims data... you get the point). No errors are thrown anywhere in the external registration/login process; it's just as if my application does not know which context to look at for User.Identity information.

Here is the body of my ConfigureServices in Startup.cs:

services.AddAuthentication(sharedOpts =>
{
    sharedOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(opts => Configuration.Bind("AzureAd", opts))
.AddCookie();

services.AddIdentity<AspNetUsers, AspNetRoles>()
   .AddEntityFrameworkStores<CoreUMContext>()
   .AddDefaultTokenProviders();

services.AddMvc();

string dataConnection = Configuration["ConnectionStrings:TrackerDatabase"];
string userConnection = Configuration["ConnectionStrings:UserDatabase"];
Trace.TraceWarning("Connecting to connection at: " + dataConnection);
try {
    services.AddDbContext<EdgeContext>(options => options.UseSqlServer(dataConnection));
    services.AddDbContext<CoreUMContext>(options => options.UseSqlServer(userConnection));
}
catch (Exception ex) {
    Trace.TraceError("Error connecting to DB: " + ex);
}

I know middleware can be tricky with the order you add to the pipeline, so I've tried every combo (.AddIdentity before .AddAuthentication, adding CoreUMContext before the EdgeContext, etc.). Injecting the Identity context (CoreUMContext) into my controllers does not solve the issue either.

Again, this line works

var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);

and result.Succeeded returns true, so to some degree it's working, but I can't seem to find good literature on the exact path User.Identity follows to resolve itself.

I'd greatly appreciate if anyone can shed light, as I'd really like to not have to go back to an all-in-one-db or all-in-one-context scenario as this doesn't really fit my architectural requirements.

UPDATE

It seems that the issue is Chrome not storing/sending the property cookie Identity uses to resolve which user is making the request. It works in Microsoft Edge however. This switched at some point for me yesterday and I'm still trying to deduce why (clearing cookies has not worked), but it appears the multiple contexts were not the cause of the problem.

broccoli_rob
  • 520
  • 5
  • 16
  • Where and when exactly are you checking for `User.Identity`? – Camilo Terevinto Jun 28 '18 at 21:14
  • Assuming that your identity datastore is in CoreUMContext, when signin occurs do you see database activity using SQL Server Profiler? Other issue could be to check that cookie is being generated which can be confirmed using Fiddler. – Mark G Jun 28 '18 at 22:19
  • @CamiloTerevinto Both in the check in the view for my _LoginPartial and in the Index method of my Home controller (which I tried injecting the context into). – broccoli_rob Jun 28 '18 at 23:17
  • If you're using Azure AD why are you configuring Identity and why do you have a separate DbContext for it? – Brad Jun 28 '18 at 23:21
  • @MarkG I do see database activity, and when I registered through this way (I've blown the registration data out a few times and refilled it through the CoreUMContext). I will look into using Fiddler to check for the cookie; that hadn't occurred to me. – broccoli_rob Jun 28 '18 at 23:26
  • @Brad Because we need to use internal credentials for authentication but don't want to have to manage Azure Group provisions etc. for the complicated role management we anticipate, and we'll need a separate authentication mechanism for people outside our domain. – broccoli_rob Jun 28 '18 at 23:31
  • Perhaps you could look into using IdentityServer4 as a federated identity server. – Brad Jun 28 '18 at 23:37
  • @MarkG Fiddler shows that when I hit the signin-oidc (which is the Azure Ad callback) I get 2 cookies (`.AspNetCore.Correlation.OpenIdConnect` and `AspNetCore.OpenIdConnect.Nonce`), then on the next request (Identity sign-in) I get this cookie: `.AspNetCore.Identity.Application`. The NEXT call, which is a re-route to Home after a successful login, has no cookie. Could this be the issue, and any clue what would cause this behavior? – broccoli_rob Jun 29 '18 at 12:40
  • When you say the NEXT call, I assume you mean the subsequent GET to Home from the browser has no cookie in the request headers? If so, it means that the Set-Cookie response header from the login wasn't accepted. This could be due to [Strict Secure Cookies](https://www.chromestatus.com/feature/4506322921848832). – Mark G Jun 29 '18 at 15:35
  • @MarkG Secure Cookies does sound like a possible culprit. On a whim I tried this out in Microsoft Edge and it looks to be working; my `_LoginPartial` recognizes my `User.Identity` and Fiddler reports a cookie being sent in the home page request, unlike Chrome. What confuses me is it worked in Chrome yesterday before I split the contexts. Either way, I'm not sure if I should edit the OP or mark as answered; the full problem has not been solved but the issue is not the originally stated one (i.e. not a problem of the dbcontext but rather a cookie issue) – broccoli_rob Jun 29 '18 at 16:02
  • You can just append an update section to the end of your question. For Chrome you can clear old cookies for a site by going to chrome://settings/content/cookies and selecting "See all cookies and site data". – Mark G Jun 29 '18 at 16:12
  • Yea, I've tried clearing the cookies (an old user cookie would have been a sensible source of the problem!) but the new cookie, set after a fresh login, still doesn't seem to come back from Chrome. I'll look more into secure cookies and what other browser differences could be causing this. – broccoli_rob Jun 29 '18 at 17:49

1 Answers1

0

It appears this WAS a Chrome-specific issue, namely that Chrome does not agree with the setting of cookies on the domain of "localhost". After recognizing that this bizarrely was not an issue in Incognito, I found this thread: Chrome localhost cookie not being set which led me to changing my Startup.cs file:

services.AddAuthentication(sharedOpts =>
        {
            sharedOpts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOpts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddAzureAd(opts => Configuration.Bind("AzureAd", opts))
        .AddCookie(opts => {
            opts.Cookie.Domain = ""; <-- This made the difference
        });

I'm now allowing the browser to resolve the cookie domain instead of taking the default the Identity code was generating. Hopefully this does not prove to be an issue when I publish to production.

What I don't get is why this initially worked, then broke, then stayed broken even after I re-packaged my code all into one context and database; perhaps Chrome allowed a 1-time setting of the cookie but any subsequent changes were ignored.

broccoli_rob
  • 520
  • 5
  • 16
  • What version of Chrome are you using, is it possible an upgrade occurred recently? Also take a look at [open issues](https://bugs.chromium.org/p/chromium/issues/list?q=component:UI%3EBrowser%3ECookiesTree) or maybe file a new one. – Mark G Jun 29 '18 at 21:23