Microsoft Account Authentication - AuthenticationManager.GetExternalLoginInfoAsync() always null - asp.net-mvc

I have created a MVC web application and trying to use Microsoft Account authentication.
I have created the App on https://apps.dev.microsoft.com
After obtaining the App-ID and App-Secret I pasted both in my web.config
In my StartupAuth.cs I have configured Microsoft authentication with this code snippet:
var microsoftOptions = new MicrosoftAccountAuthenticationOptions()
{
ClientId = Config.MSAppId,
ClientSecret = Config.MSAppSecret,
};
app.UseMicrosoftAccountAuthentication(microsoftOptions);
It is possible to authenticate with my Windows account
In the account controller I am trying to create the account from data returned from the authentication provider. The snippet below only shows the important part.
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var userName = string.Empty;
var eMail = string.Empty;
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Index", "Home");
}
// Benutzer mit diesem externen Anmeldeanbieter anmelden, wenn der Benutzer bereits eine Anmeldung besitzt
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
...
await AuthenticationManager.GetExternalLoginInfoAsync() always returns null.
The configuration in the developer portal looks like this:
I have tried to add scopes for e-mail and basic information, but this seems to be deprecated and the authentication page shows an error.
In the debug console in Visual Studio I get the following error:
Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationMiddleware Error: 0 : Authentication failed
System.Net.Http.HttpRequestException: Der Antwortstatuscode gibt keinen Erfolg an: 400 (Bad Request).
bei System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
bei Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationHandler.d__4.MoveNext()
I have created a clean test project from scratch, to find out what the cause for the problem is. This is what I have found out:
After the initial configuration of the authentication all Microsoft.Owin.Security.* components are on Version 3.0.1 and Microsoft Account authentication (not Windows Azure authentication - this is not part of the problem) works fine
After updating Microsoft.Owin.Security.* to Version 3.1.0 some changes happen with Microsoft Account authentication. The logon experience changes. After clicking he login button, it redirects to the azure logon experience, unlike before, where I was redirected directly to the Microsoft Account logon experience and authentication stops working.
After this I left all Microsoft.Owin.Security.* components on Version 3.1.0 and downgraded Microsoft.Owin.Security.MicrosoftAccount back to 3.0.1. And voila, I got the "old" logon experience back, with the result, that authentication worked again.
The Problem must be in Microsoft.Owin.Security.MicrosoftAccount.
Does anyone know - except from using Version 3.0.1 - how to solve the issue or is this a problem for Microsoft support?

Related

MVC 5 openid connect on-premises ADFS 4.0 logout issue

I am trying to setup a new MVC 5 project and a new 2016 server with ADFS. I can authenticate with Oauth 2 and OpenID Connect. The issue I have is when I try to logout of the application. The sign out link points to the default /Account/SignOut action. When that action is called I get redirected to the post logout redirect uri which is https://website.com. This redirect loops until the browser errors out with "website redirected you too many times".
This is my signout method.
public void SignOut()
{
string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
// Send an OpenID Connect sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType
);
}
If I just call
HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType)
the logout fails but at least the constant redirects don't happen.
Is there an overload of the Authentication.Signout() method which logs me out and then redirects to the post logout redirect uri?
I worked with MS support and determined that Sign Out is not currently supported for OID Connect with ADFS 2016. It may be part of a future release, but as of now there is no set release date for support.
ADFS 4.0 (on Windows Server 2016) supports "Single log-out". If you use Microsoft.AspNetCore.Authentication.OpenIDConnect package (this is the newer one, for ASP.NET Core) it works seamlessly.
If you query the well known endpoint for OpenID Connect configuration, which is https://your-server/adfs/.well-known/openid-configuration, you will find a key end_session_endpoint which contains the logout URL you should use.
Also, you can check the documentation: https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/development/ad-fs-logout-openid-connect
For instance, in order to invoke it from your controller, you would would use an action method like this one:
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
You might find this sample useful (even though it is for Azure ADFS, it works for local installs as well): https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore

Google OAuth on MVC5 ExternalLoginCallback?error=access_denied

I have set up my Google OAuth
And I have added the code into Startup.Auth.cs
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
// LRC
ClientId = "xxxxxxxxx",
ClientSecret = "xxxxx"
//CallbackPath = new PathString("/signin-google")
});
But after I chose a google account to log in, it redirected me to the login page again,
I checked the network via Chrome and found that the access was denied.
http://www.liferunningclub.com.au/Account/ExternalLoginCallback?error=access_denied
I cannot figure it out.
Update
Now I did something else:
I added an annotation ([RequireHttps]) on the Account Controller
I enabled the SSL for my project.
I updated the url and re-direct url in Google Console to https
Tried to log in with Google, after I selected my Google account, it returned the same access_denied.
It would be better if the response from Google could give more detailed information.
I had the same problem using the latest ASP.Net MVC template with "Individual Accounts" selected.
The solution was to enable the Google+ API for my project in the Google Developer console.
I found my answer here (scroll down to "Changes to Google OAuth 2.0...").
The same error happened to me for Facebook provider.
Turns out the solution was as simple as updating the nuget package to 3.1.
It turns out that Facebook did a "force upgrade" of their graph API
from version 2.2 to 2.3 on 27th March 2017
For the record I'm using the following:
http://localhost:58364 in iisexpress with NO https
In Facebook I have the following settings configured for a test app:
In addition if you're using a sample template the error parameter returned isn't being consumed which can be misleading. You should add string error to ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl, string error)
{
if (error != null)
{
return View("Error");
}
I had this problem as well. After I enabled the Google+ API the problem is not solved yet. Turns out I haven't set the 'Authorized JavaScript origins' in my google API console. So I set the authorized javascript origins, and the problem solved.
I had the same issue. I had Google+ API active and set JavaScript providers. Turns out that my version of Microsoft.Owin 3.1 was too old. I've updated every single nugget which had Microsoft.Owin.(whatever) in it's name and it started working fine (version 4.1)
Hope it helps!
This is most likely because you have not enabled the Google + API in the developer console.
So when your account trys to get the details about the Google Account, it says access_denied.
Simply go to the developer console and enable the Google + API
None of the above solution worked for me. Turns out In my case I was tweaking with Google OAuth Playground and I added https://developers.google.com/oauthplayground this url in Authorized Redirect Uris section of my Google Credentials for Client ID and Secrets.
When I removed it and retried, it worked fine.
PS: I had to reset the OAuth Playground settings that I had modified too.
EDIT
The other issue was, my code threw an Exception when the user was OnAthenticated EventHandler was triggered. Turns out a null reference which was resulting in access_denied status being returned.
GoogleOAuth2AuthenticationOptions googleOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxxxx.apps.googleusercontent.com",
ClientSecret = "XXXX",
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = (context) =>
{
try
{
TokenHelper tokenHelper = new TokenHelper();
// Any exception here will result in 'loginInfo == null' in AccountController.ExternalLoginCallback.
// Be sure to add exception handling here in case of production code.
context.Identity.AddClaim(new Claim(tokenHelper.AccessToken, context.AccessToken)); // From This line and onwards. tokenHelper's properties were null.
// For clarity, we don't check most values for null but RefreshToken is another kind of thing. It's usually
// not set unless we specially request it. Typically, you receive the refresh token only on the initial request,
// store it permanently and reuse it when you need to refresh the access token.
if (context.RefreshToken != null)
{
context.Identity.AddClaim(new Claim(tokenHelper.RefreshToken, context.RefreshToken));
}
// We want to use the e-mail account of the external identity (for which we doing OAuth). For that we save
// the external identity's e-mail address separately as it can be different from the main e-mail address
// of the current user.
context.Identity.AddClaim(new Claim(tokenHelper.Email, context.Email));
context.Identity.AddClaim(new Claim(tokenHelper.Name, context.Name));
context.Identity.AddClaim(new Claim(tokenHelper.IssuedOn, DateTime.Now.ToString()));
context.Identity.AddClaim(new Claim(tokenHelper.ExpiresIn,
((long)context.ExpiresIn.Value.TotalSeconds).ToString()));
return Task.FromResult(0);
}
catch (Exception ex)
{
throw;
}
},
},
AccessType = "offline",
UserInformationEndpoint= "https://www.googleapis.com/oauth2/v2/userinfo"
};
Default Google authentication no longer works, you can add updated Owin.Security.Provider.Google package through NuGet or find it here
Try to use https:// instead of http:

LinkedIn Authentication via Oauth2 returns null result (error=access_denied)

I moved my ASP.NET MVC web application from membership to Identity authentication and since that I cannot authenticate on LinkedIn anymore.
The Facebook authentication is still working fine but the LinkedIn is always returning a null loginInfo after the GetExternalLoginInfo call.
For the LinkedIn I'm using the Owin LinkedIn provider: LinkedIn APIs for .NET. I also unsuccessful tried to follow this post from Jerrie Pelser.
The Application calls the ExternalLogin Action that executes the ExecuteResult method and calls back the ExternalLoginCallback (after I allow access to the application). As I stated before, the method AuthenticationManager.GetExternalLoginInfoAsync() always returns a null loginInfo.
I checked the application settings in the LinkedIn and everything seems to be OK.
Ops! I almost forgot to say that the LinkedIn is returning back the URL with a generic error message: "GET /Account/ExternalLoginCallback?error=access_denied HTTP/1.1"
I can Authenticate using the DotNetOpenAuth.Clients (hosted github) but I'd like to just use the Identity.
Startup.Auth.cs
var linkedInOptions = new LinkedInAuthenticationOptions();
linkedInOptions.ClientId = "Xxxxx";
linkedInOptions.ClientSecret = "Yyyyyyy";
linkedInOptions.Scope.Add("r_fullprofile");
linkedInOptions.Provider = new LinkedInAuthenticationProvider()
{
OnAuthenticated = async context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("LinkedIn_AccessToken", context.AccessToken));
}
};
linkedInOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseLinkedInAuthentication(linkedInOptions);
ExternalLogin
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 }));
}
CallBack Action
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
LinkedIn CallBack URI
http://localhost:3279/signin-linkedin
After some researches and a visit the NuGet package repository I found a prerelease version of Owin.Security.Providers that worked like a charm. I just had to install it from package manager console and the issue with the null return from the LinkedIn External Login has gone.
Install-Package Owin.Security.Providers -Pre
Caution: Please be aware that the use of pre release packages may cause unexpected problems.

Azure Active Directory Sign out Error

I've created a generic website from visual studio 2013 and successfully wired it up to an existing Azure Active Directory instance for authentication. I can login as any user in the Azure AD with the appropriate credentials. Unfortunately I can not sign out without receiving this error:
AADSTS50068: Signout failed. The initiating application is not a
participant in the current session.
I've googled the error number, but apparently I'm the first person to ever encounter this....:) I'm pretty sure I'm not, but I'm now at a loss to explain why sign in works, but sign out does not.
Here is the sign out code, pretty much exactly as it was generated:
public ActionResult SignOut()
{
WsFederationConfiguration config = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
// Redirect to SignOutCallback after signing out.
string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);
SignOutRequestMessage signoutMessage = new SignOutRequestMessage(new Uri(config.Issuer), callbackUrl);
signoutMessage.SetParameter("wtrealm", IdentityConfig.Realm ?? config.Realm);
string signoutMsg = signoutMessage.WriteQueryString();
FederatedAuthentication.SessionAuthenticationModule.SignOut();
return new RedirectResult(signoutMsg);
}
After deleting the website in Azure and the associated live.com registration for Microsoft OAuth (https://account.live.com/developers/applications/) I recreated the example and logout worked as expected. I'm pretty confident I had incorrectly registered the application with an invalid "Redirect Url" (http vs https).

Securing a Web API with Windows Server 2012 R2 ADFS 3.0 and Katana

I would like to make a MVC Web Application that talks to a Web API application and use ADFS 3.0 (on Windows 2012 R2) for authentication.
I managed to make the MVC Web Application to authenticate using ADFS.
and configured everything as shown in this article by Vittorio Bertocci
http://www.cloudidentity.com/blog/2013/10/25/securing-a-web-api-with-adfs-on-ws2012-r2-got-even-easier/
Now I use the latest pre release of AAL from nuget
Now after authenticating with ADFS from web MVC app, I try to call the webapi
public async Task<String> CallSecuredAPI()
{
string authority = "https://fs.domain.com/adfs";
string resourceURI = "https://{hostheader}/SecuredAPI";
string clientID = "ExternalWebSite1";
string clientReturnURI = "https://{hostheader}/ExternalSite";
AuthenticationContext ac = new AuthenticationContext(authority, false);
AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI));
string authHeader = ar.CreateAuthorizationHeader();
var client = new HttpClient();
HttpRequestMessage request =
new HttpRequestMessage(HttpMethod.Get, "https://hostheader/SecuredAPI/api/Claims");
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = await client.SendAsync(request);
string responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
but I get this error which I think is with the client not being a UI based client or WPF , windows App. Can someone let me know whether I am doing something wrong.
![Error when trying to get Authorization code using AAL][1]
Server Error in '/ExternalSite' Application.
Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.
Source Error:
Line 43:
Line 44: AuthenticationContext ac = new AuthenticationContext(authority, false);
Line 45: AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI));
Line 46:
Line 47: string authHeader = ar.CreateAuthorizationHeader();
Source File: c:\Users\balakrishna.takkalla\Documents\Visual Studio 2013\Projects\ExternalSite\ExternalSite\Controllers\HomeController.cs Line: 45
Stack Trace:
[InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.]
System.Windows.Forms.Form.ShowDialog(IWin32Window owner) +5701502
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialog.ShowBrowser() +18
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialog.OnAuthenticate() +23
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.WindowsFormsWebAuthenticationDialogBase.AuthenticateAAD(Uri requestUri, Uri callbackUri) +284
Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.InteractiveWebUI.OnAuthenticate() +103
Microsoft.IdentityModel.Clients.ActiveDirectory.OAuth2Request.SendAuthorizeRequest(Authenticator authenticator, String resource, Uri redirectUri, String clientId, String userId, PromptBehavior promptBehavior, String extraQueryParameters, IWebUI webUi, CallState callState) +363
Microsoft.IdentityModel.Clients.ActiveDirectory.<>c__DisplayClass9b.<AcquireAuthorization>b__9a() +111
System.Threading.Tasks.Task.Execute() +110
if I understood correctly: you want to access a Web API from the code-behind of an MVC application.
That topology is possible with Azure Active Directory today, you can see that in action in the sample https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet (I am in the process of updating it to the latest ADAL refresh, you can take a peek at the RCUpdate branch to see the work in progress).
However that topology is NOT achievable today from ADFS WS2012 R2. The reason is that an MVC app (and any other web site) is a confidential client, which OAuth2 handles differently from a public client (the WPF app you used as a starting point is a public client). In the scenario you are targeting, to use ADAL for getting a token from a confidential client you would use ADAL's method AcquireTokenByAuthorizationCode (see the sample I mentioned). However ADFS WS2012 R2 is incapable of processing that method. Today the OAuth2 support in ADFS WS2012 R2 is limited to public clients only.
Sorry for bringing bad news! As a mitigation, you might consider federating your ADFS with an AAD tenant: at that point you would be able to do what you want, authenticating as an ADFS user but getting tokens from AAD (which does support the necessary OAuth2 grant).
HTH
V.

Resources