Owin Facebook Login failure on MVC Azure Production site ONLY - asp.net-mvc

The short version:
I have an MVC5 website app deployed as an Azure cloud service web role. Using Owin for a login flow. The Owin Facebook integration works fine when testing the site on localhost, but on the production server GetExternalLoginInfoAsync() is returning null in the callback from signin-facebook.
Some details:
Have all the latest Owin Nuget packages (4.0.1 other than Identity.Owin 2.2.2 and Owin (startup components) 1.0).
The app uses basic Owin cookie authentication (traditional logins work fine).
We are not using the default Owin SigninManager, or UserManager as per the MVC WebApplication template. (Failure occurs well before reaching any of that code, in theory!)
Testing using Chrome on Windows 10.
It works perfectly on localhost (both debug and release), directly running the web project from visual studio (no cloud service involvement).
Am using the same Facebook test user for both localhost and production.
On the Facebook end, everything looks normal and the app is added to the user's list of apps, with all the permissions. It's not a facebook rejection.
Went so far as to remove Application Insights (as that is something different in production), but it did not affect the problem.
Looking at DevTools in Chrome:
Cache control looks normal for everything, all no-cache.
The .AspNet.Correlation.Facebook cookie is set and is being returned (same value) to the signin-facebook endpoint on completion from Facebook. Note that there is no other Owin related cookie.
Re. Deployment:
The Azure cloud service consists of the single web role.
Azure cloud service osFamily="5" osVersion="*"
Deployment is done directly from Visual Studio to the staging slot (right click publish).
I typically perform an interactive login to the staging slot, and when things are warmed up I use the Azure portal to swap the slots. Testing of the Facebook login is only on the production slot (not staging).
Here is a code fragment showing the processing (ChallengeResult is per the current WebApplication template code). The Controller is marked as [OutputCache(Location = OutputCacheLocation.None)] and is not marked for [Authorize]:
// POST: /Membership/ExternalSignupDispatch (They clicked to login with Facebook, Google, etc.)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ExternalSignupDispatch(string provider, string community = Communities.Dating)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalSignupCallback1", "Membership", new { community }));
}
// GET: /Membership/ExternalSignupCallback
public async Task<ActionResult> ExternalSignupCallback1(string community = Communities.Dating)
{
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null) // Unsuccessful login
{
The silent failure of Owin/Facebook on the production server is maddening. If only there were an error enumeration... or an exception... but alas
Any ideas are greatly appreciated!

So. It turns out that Owin will currently fail in mysterious manners if there is no pre-existing ASP.NET_SessionID cookie present. Without it, the signin-facebook function does not remove the .Aspnet.Correlation.Facebook cookie, and does not set the .Aspnet.ExternalCookie cookie. The non-presence of the Session ID cookie somehow prevents the required cookie processing from taking place. All this sets the stage for intermittent silent failures, depending on the client's cookie status.
The workaround is to store a fake Session variable when generating the form with the Facebook login, forcing creation of the SessionID cookie prior to any Owin logins.
Note we are using the SystemWebCookieManager (in the hopes of avoiding such cookie issues). It appears that things are still evolving.
For reference, here is the cookie setup in our ConfigureAuth function:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager(),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
ExpireTimeSpan = TimeSpan.FromMinutes(Params.LoginExpiryMinutes),
SlidingExpiration = true,
LoginPath = new PathString("/Login"),
Provider = new CookieAuthenticationProvider // Used to allow returning 401 Unauthorized status to API calls, instead of 302 redirect
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

Related

GetExternalLoginInfo() returning null

I have registered my mvc app with https://apps.dev.microsoft.com/ and (when on localhost),after updating all NuGet packages managed to authenticate users (using a clientid and secret) using microsoft authentication - that works fine! lets forget the time wasted to discover that I had to use https://localhost:xxxx/signin-microsoft - thought I had to supply my callback method endpoint.
Now, I had to do the same thing however authenticating users with my app registered on Azure Active Directory in the section App Registrations.
Note: Users are not registered in azure but on a different domain however using microsoft authentication. I just changed the client id and secret to specify those generated on azure while registering the app. My Callback method is being accessed after signing in HOWEVER, the loginInfo object which I need to read the email of my user is NULL. I made sure i had the latest updates of packages, and i tried to search spending 3 days finding only applications which make use of tenants id, authority etc.
I just need to use individual accounts signing with microsoft authentication with an application registered on AAD (Azure active directory - registered apps section). I know it works because i've seen it working with php on other apps, but with microsoft code/libraries its not.
btw i tried adding scopes, calling synchronous to no good. I also tried inspecting the incoming data and its saying access denied and i'm pretty sure that client id and secret are ok. maybe the reply url is wrong? but any other callbacks i supply result in bad request and the callback method is at least being triggered. I also enabled "Sign Users In" and "Sign in and read users profile" permissions from azure as well. I'm running out of ideas. any help would be much appreciated. thanks
Code is the same code which is given to you when creating a new mvc web application using individual accounts i.e. In Startup.Auth.cs: i have this important part and other code
var myObj = new MicrosoftAccountAuthenticationOptions()
{
ClientId = "xxx",
ClientSecret = "xxx",
};
myObj.Scope.Add("openid");
myObj.Scope.Add("email");
// myObj.Scope.Add("User.Read");
app.UseMicrosoftAccountAuthentication(myObj);
In AccountController.cs i have this method which is initializing the request to microsoft
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider,
Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl
}));
}
and this (part of a) method which is handling the callback response:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await
AuthenticationManager.GetExternalLoginInfoAsync();
//...
}
ok solved. the solution i provided earlier in my question works for an application registered on https://apps.dev.microsoft.com/ . when i registered my app on AzureAD it had to be done in a different way following this method: https://learn.microsoft.com/en-us/azure/active-directory/develop/guidedsetups/active-directory-aspnetwebapp

ADFS OWIN authentication not invoked in local IIS

I am trying to determine how to get an MVC application set up to authenticate against an on-premise ADFS server. I am using the Azure AD sample found at the Github Azure Samples and altered it as described in Vittorio Bertocci's blog to use my app's RealmId and my organization's ADFS metadata endpoint. When I run the sample using IIS Express, the OWIN middleware is invoked and I get the redirect to the ADFS login screen.
However, The app I am working on needs to run in IIS so I am trying to configure my sample app in local IIS. When I run the sample in local IIS, there is no redirect to ADFS and the windows (Kerberos) identity is returned instead. How do I make sure that the OWIN middleware is invoked by IIS? The app pool is running in Integrated v4.0 mode.
This is the code in my Startup.Auth.cs:
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string metadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = metadata,
Notifications = new WsFederationAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("Home/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
What is the Authentication tab in IIS set to for your application?
Have you got Forms or Windows Authentication enabled?
What happens if the only item enabled is Anonymous?

AuthenticationManager.GetExternalLoginInfoAsync() on google aspnet mvc5 returns null

I've developed an ASPNET MVC 5 application using default Visual Studio 2015 Template and Google authentication. All works fine in the development environment but on real the call after external authentication AuthenticationManager.GetExternalLoginInfoAsync() sometimes returns null.
Normally it returns null on the central hours of the day (from 08:00 to 20:00) but I haven't found a pattern because sometimes works at that time. I've watched the developer console, but there are not a lot of requests (22 in the last 12 hours) and all are successful.
I've tried some solutions from other StackOverflow threads but they didn't work. Also, I can try them only on the night because is a personal project and then the connection is successful and I can't reproduce the issue.
The code is standard:
On Startup
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.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
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
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>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var google = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxxx",
ClientSecret = "xxxx",
Provider = new GoogleOAuth2AuthenticationProvider()
};
google.Scope.Add("email");
app.UseGoogleAuthentication(google);
}
On ExternalLoginCallback
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
Log.Debug("AuthenticationManager.GetExternalLoginInfoAsync()");
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
Log.Error("AuthenticationManager.GetExternalLoginInfoAsync(): null");
return RedirectToAction("Login");
}
...
More info
I've created new Google credentials with another user and when I change the clientId and clientSecret, it works again... I don't know even when...
Yet more info
The problem is not on credentials, I "only" need to restart ASP.NET application to solve the issue, perhaps this new clue helps somebody to help me.
Not replicated
I've post the answer and it isn't in that OWIN's GetExternalLoginInfoAsync Always Returns null post, I've mentioned there the thread where I found the solution: ASP.NET_SessionId + OWIN Cookies do not send to browser
Finally (I think that) I've found the solution after a week with no failed login. All thanks to this StackOverflow thread. My solution has been inserting the following line on AccountController.ExternalLogin action:
Session["Workaround"] = 0;
In the thread above (and links provided there) found a better explanation of the bug when mixing sessions and cookies of ASPNET MVC and OWIN component.
Full controller service code:
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser
Session["Workaround"] = 0;
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
I faced the Similar Issue with Visual Studio 2017 and .net MVC 5.2.4, Updating Nuget Microsoft.Owin.Security.Google to latest version which currently is 4.0.1 worked for me
Nothing worked for me tried all above combination...
But when updated all references dlls with latest on NUGET
it worked like charm!!
After updating Microsoft.Owin.Security.Google version 4.1.1 (I'm using .NET framework 4.8) it works for me
I updated to version 4.0.1 of the Microsoft.Owin.Security.Google package and I was having the same problem. I was getting null and access_denied. Turns out the problem was due to a space that I had copied over in the Client Secret. It took me 2 days and lots of unnecessary code changes to figure this out. I noticed that when you click copy (not just copy) and paste from Google, there is a space at the end of the Client Secret.
I had the same trouble going from version 4.0.1 to 4.1.1 of the Microsoft.Owin.Security.* packages.
This article SameSite in code for your ASP.net applications seems to offer some clues as to how to make set SameSite to avoid this error, but due to time pressure, I ended up having to go back to 4.0.1 and will have to revisit later.

Double authentication with Web app & Web API needed?

I'm stuck on how to solve following problem.
I'll start with describing what my app looks like in a general context.
[ ASP MVC (Angular App) ]
Uses Owin cookie
[ WEB API 2 ]
Uses Oauth Token Bearer
This scenario is happening:
User visits app and authenticates with a login form which lies in ASP MVC app and generates a cookie.
Now I've decided to use AngularJs to add a couple features which made me use $resources and Web API 2. However, those features are only available if user is authorized.
To the problem: Now I must use a token for each request to the Web Api 2 to access different methods within controllers. This means I must login the user again but this time through AngularJs. Using /token route.
How would I do this?
Should I take the cookie, check credentials in it and send it as a authentication request?
Can I do something within the form authentication, in the same method, in the Asp MVC app?
Please help me, this gave me a lot of overhead. Walking from a simple app to this in 30min. Can't even get my head around all stuff in the authentication.
Regards!
My WebAPI supports both token and cookie auth.
During startup I register the authentication like this:
private void ConfigureAuth(IAppBuilder app)
{
//Token
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
});
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
// this is to ensure that a 401 response is sent if the
// user is not authenticated, rather than redirecting to
// a logon page.
}
},
CookieDomain = ".example.com" //might not need to set this
});
}

Asp .Net Identity 2 OAuth via Google+ error in production server

I am getting the problem when I use Asp .Net Identity 2 connect Google+. Currently, my website connect Google+ via OAuth API 2 and use Claim Identity store some information of users. I was setup fine in running localhost with https. But the problem appear when I deploy my website into production server. After use accept to provide permission for my website to get his information, Google+ callback my website with url /signin-google. And after that, ASP .Net MVC automatically redirect to ExternalCallBack action but it is get error ?error=access_denied. I have capture the network and see information below:
In url callback signin-google:
signin-google?state=nNABsQBmwoPILh1mViOUIqzDcxQIS3HVZx2jtrSYCwd-ifMn4bDgBV1H1qdewFZx5Lz1c35ZZEpUem9jDTUrKlzWDuV-MwTQ3Tesx66PEjWdQQHo0QPJHX_bRMHqgN-Ad1whLs4iUyUSCH39oeTvYg3Cx6O0_v7Sc5GaUujHgr6xW1jw8EImhWJgnFGXgkAjD5hOtr7RoYO23xJyw0AIyuWnyx1gInJndWKvL-eqWPD9BtRaNe3nhWF5NGEG_2Ir&code=4/OEwsZCeeDPKrN5Dls3Uu-Q0wacdMqlhdbb8B1P__8X8.MmSgAQ_-cmIRgrKXntQAax20FxCmlQI
Asp.Net.Collrelation.Google nXgdr60bDh6tfivnvc6NA6ubz1K9zwjOqgrBBQgsitE
In URL callback external-login:
external-login-callback?error=access_denied
Hope you help me.
*Edit:
This is my code in Startup.Config.cs
var googleOpt = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxxxxxxxpm0il.apps.googleusercontent.com",
ClientSecret = "xxxxxxxx",
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("GoogleAccessToken", context.AccessToken));
return Task.FromResult(o);
}
}
};
googleOpt.Scope.Add("https://www.googleapis.com/auth/plus.login");
googleOpt.Scope.Add("https://www.googleapis.com/auth/plus.profile.emails.read");
googleOpt.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseGoogleAuthentication(googleOpt);
And in my account controller I use default code of Asp .Net MVC 5.2
I had the same issue and had to enable the Google+ API in the Google Developer Console.
If this is turned off, it will not work. As soon as I turned it on, everything was fine.

Resources