GetExternalLoginInfo() returning null - asp.net-mvc

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

Related

Broken AntiforgeryToken with Microsoft MSAL

I'm working with ASP.Net MVC and I have a problem using MSAL while authenticating a User. This is because, as we use AntiforgeryToken, when the user sign in in the page of Microsoft, the token breaks and we get an error related to the token.
My question is, is there a way to keep the token even after being redirected from Microsoft login page? Or can I recreate it?
I've search on other questions and google and found nothing.
Thank you.
Yes you can save the token in your application like this:
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public async Task<IActionResult> Profile()
{
// Acquire the access token.
string[] scopes = new string[]{"user.read"};
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
context.Token = accessToken;
}
Alternatively, you can explicitly acquire tokens by using the acquire-token methods as described in the core MSAL library. The MSAL wrapper provides the HTTP interceptor, which will automatically acquire access tokens silently and attach them to the HTTP requests to APIs.

Owin Facebook Login failure on MVC Azure Production site ONLY

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);

OAuth and OWIN Authentication - Confusion

I have been asked to create a 'Authentication/Authorization' Middle man or broker as an http,MVC web application, so that this can be used to multiple applications on our organization for authentication/Authorization purposes. Means, users will signup, Login on this broker application and once confirmed Authenticated, authorized user, he will get redirected to client applications accordingly. This is the use case.
I am choosing OAuth and OWIN to develop this broker in an MVC applicaiton, which means OAuth(Authorization) will issue access token + refresh token, once user is successfully authenticated. I use normal, simple, minimal authentication logic inside the Oauth Authorization Server's Login Controller as below :
public class AccountController : Controller
{
public ActionResult Login()
{
var authentication = HttpContext.GetOwinContext().Authentication;
if (Request.HttpMethod == "POST")
{
var isPersistent = !string.IsNullOrEmpty(Request.Form.Get("isPersistent"));
if (!string.IsNullOrEmpty(Request.Form.Get("submit.Signin")))
{
var user = Constants.Users.UserCollection.Where(u => u.Email.ToLower() == Request.Form["username"].ToLower().Trim() && u.Password == Request.Form["password"].Trim());
if (user.Count() > 0)
{
authentication.SignIn(
new AuthenticationProperties { IsPersistent = isPersistent },
new ClaimsIdentity(new[]
{ new Claim(ClaimsIdentity.DefaultNameClaimType, Request.Form["username"]),
new Claim("DisplayName", user.FirstOrDefault().DisplayName) } , "Application"));
}
}
}
return View();
}
This is the MSFT sample application I am following to develop this conceptual application.
https://learn.microsoft.com/en-us/aspnet/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server.
My question here is : I read in many articles like, its not good to use Oauth authentication, else use OPENID Connect handling authentication. To be frank, I am not used with OPENID Connect and I am not sure about the necessity of creating a OPENID Provider for my organization, Since this service will be used only by customers of our organization - less than 200,000 users. We hardly need a user signup and login, this account need to be used among different web applications of our organization. Please help me here with your inputs. Thanks in advance.
I think your question is about the benefits of OpenID Connect (OIDC) over OAuth 2.0.
OIDC builds upon OAuth 2.0 so you can use all of it's features. In a practical context, the question you should ask yourself is: Do other applications (clients, APIs), which use your "broker" (authorization server/security token service/OpenID provider) need to know something about the user, who just logged in? Do they need the ID, it's roles, username etc..? If the answer is no and you just need a signed token you are probably better of with OAuth.
If you start to include user claims (=attributes) in your access token you should at least have a look at OIDC. Also note, that even if you include claims in your access token, these are meant for the resource server (=API) and are normaly inaccessable for the client (unless you extract them and expose them on the API side - this is basically what the OIDC userinfo endpoint does).

Microsoft Graph API returning 403 to any request

I'm working on an application that, in this point, will retrieve the Office Groups that the logged in user is included and perform actions based on that info.
I'm using oAuth2.0 and the v2.0 token endpoint to get access without a user, and with the code below, I can provide administrator consent to the permissions (which were applied to the application permissions on the new Application Registration Portal https://apps.dev.microsoft.com/ and appear on the Enterprise Applications section on Azure), request the token to Azure and receive it, but even with the permissions applied and that token, I get a 403 response code (Insufficient privileges) from the Graph API to any request I try to perform.
The code for those actions is the following:
// Request Admin Consent
HttpRequestMessage adminConsentRequest = new HttpRequestMessage(HttpMethod.Get, "https://login.microsoftonline.com/" + TenantId + "/adminconsent?client_id="+ClientId+"&redirect_uri=https%3A%2F%2Flocalhost%3A44369%2FHome%2F");
var adminConsentResponse = await client.SendAsync(adminConsentRequest);
// Request Token
HttpRequestMessage tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://login.microsoftonline.com/"+TenantId+"/oauth2/v2.0/token") { Content = new FormUrlEncodedContent(tokenRequestPairs) };
var tokenResponse = await client.SendAsync(tokenRequest);
string tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync();
var deserializedTokenResponse = (JObject)JsonConvert.DeserializeObject(tokenResponseBody);
string accessToken = deserializedTokenResponse["access_token"].Value<string>();
// Call Microsoft Graph API
HttpRequestMessage graphRequest = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/memberOf");
graphRequest.Headers.Add("Authorization", "Bearer "+accessToken);
var graphResponse = await client.SendAsync(graphRequest);
string graphResponseBody = await graphResponse.Content.ReadAsStringAsync();
var deserializedGraphResponse = (JObject)JsonConvert.DeserializeObject(graphResponseBody);
Enterprise Application permissions on Azure
APP Registration Portal permissions
Can someone guide to any kind of mistake I'm making?
With the authorization token and the permissions applied, I can't see why would I get an AccessDenied response.
It's been more than 48 hours since I applied the permissions, so it's not a sync problem.
Update: So thanks to #juunas I managed to reapply the permissions and the token now shows all the permissions applied on the Application Portal (User.Read.All, Directory.Read.All and Group.Read.All), but the API still returns 403 status code (Authorization_RequestDenied).
I've tried another endpoint without the /me just to make sure that is not a reference problem, but it also returns 403 status code.
One thing that is funny is that the App was registered on the new app portal as I said, and it appears on Enterprise Applications on Azure, but not on my App Registrations, so I can only alter permissions on the new App Portal. It should be like this, since I'm using a new registration portal?
After a discussion in the comments, the problem was fixed by re-consenting the permissions similarly as shown in my blog post: https://joonasw.net/view/the-grant-requires-admin-permission (though it is written for v1).
To run admin consent again, you need to add prompt=admin_consent to the authorize URL.
Okay, so a few minutes after the update on the original post, the token was accepted by the endpoints.
The only problem is that the graph API does not recognize the ID of the user logged in to use the /me endpoints, but I bypassed that using the /{group-id}/members endpoint (in my case, it's not how I wanted but solves my problem).
Thanks #juunas for the help!

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.

Resources