Azure Active Directory Sign out Error - asp.net-mvc

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

Related

ASP.NET Core Identity x Docker - Confirmation link invalid on other instances

I am currently developing a web API with ASP.NET Core, using Microsoft Identity Core as for the identity management. When a user registers, it is sent an email with a confirmation link - pretty basic so far.
The problem comes when publishing my API to Azure using a containerized Azure App Service, and when setting the number of instances to 2 or more. The confirmation link seems to be working only half the time; tests on my dev machine with multiple Docker containers running seemed to confirm that fact, as the confirmation link could be validated only on the instance the user had registered on (hence the instance where the confirmation link was created).
Having dug a bit on the subject by reading this article by Steve Gordon, and explored the public GitHub code for Identity Core, I still don't understand why different container instances would return different results when validating the token, as the validation should mainly be based on the user SecurityStamp (that remains unchanged between the instances becauses they all link to the same database).
Also, enabling 'debug' logging for the Microsoft.AspNetCore.Identity only logged
ValidateAsync failed: unhandled exception was thrown.
during token validation from the DataProtectorTokenProvider.ValidateAsync() method from AspNetCore.Identity, so it is not very helpful as I can't see precisely where the error happens...
May this be linked to the token DataProtector not being the same on different instances? Am I searching in the wrong direction? Any guess, solution or track for this?
Help would be immensely appreciated 🙏
Here is some simplified code context from my app for the record.
UserManager<User> _manager; // Set from DI
// ...
// Creating the user and sending the email confirmation link
[HttpGet(ApiRoutes.Users.Create)]
public async Task<IActionResult> RegisterUser(UserForRegistrationDto userDto)
{
var user = userDto.ToUser();
await _manager.CreateAsync(user, userDto.Password);
// Create the confirmation token
var token = await _manager.CreateEmailConfirmationTokenAsync(user);
// Generate the confirmation link pointing to the below 'ConfirmEmail' endpoint
var confirmationLink = Url.Action("ConfirmEmail", "Users",
new { user.Email, token }, Request.Scheme);
await SendConfirmationEmailAsync(user, confirmationLink); // Some email logic elsewhere
return Ok();
}
// Confirms the email using the passed token
[HttpGet(ApiRoutes.Users.ValidateEmail)]
public async Task<IActionResult> ConfirmEmail(string email, string token)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound();
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded)
{
return BadRequest();
}
return Ok();
}
Token generated based on security stamp but Identity uses DataProtector to protect the token content. By default the data protection keys stored at location %LOCALAPPDATA%\ASP.NET\
If the application runs on single machine it is perfectly fine as there is no scope for key mismatch. But deployed on multiple instances the tokens will not work sometimes as the Keys are different on different machines and there is no guarantee the generation of token and validation of token will come to same instance.
To solve user redis or azurekeyvault
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-6.0#persisting-keys-with-redis

Azure AD B2C ASP.NET redirect loop

We've implemented Azure AD B2C in Umbraco on the front end using Microsofts webapp sample https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
Most of the time this is generally working, but after a while everyone starts getting hit by a redirect loop. Restating the website then clears the issue.
It seems to be something causing the .AspNet.Cookies cookie to stop being set when the user is redirected back to the site with an id token.
Any ideas?
For the folks that will run into the same problem and find this question, I wanted to share what caused this in my case and how I resolved it.
The AD B2C App Registration expects to have a RedirectURI. I forgot to put signin-oidc
So changing:
https://localhost:5000
To
https://localhost:5000/signin-oidc
resolved my problem.
This is the default value - /signin-oidc - unless something else is explicitly set.
I had infinite loop issue at logout and it was because of missing support of Razor pages. The default Microsoft.Identity.Web.UI SignOut action uses /Account/SignedOut Razor page as callback url.
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
I added Razor support in my Asp.Net core web app and it fixed the issue.
services.AddRazorPages();
and
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
Thanks.
Please ensure that your Reply URL in your application registration matches your Redirect URI in the web.config. Try setting both of these to your main homepage URL to ensure that your app is registered properly. Also make sure that the Application ID and the Client ID are matching and the right tenant is set in your web config. This needs to be the onmicrosoft.com tenant. Also, ensure that your users have the right permissions for the application.
Please follow the instructions in my blog and video to ensure that these are set properly.
https://medium.com/#marilee.turscak/reply-urls-vs-postlogoutredirecturis-in-azure-active-directory-aad-20f57a03267b
https://www.youtube.com/watch?v=A9U1VGyztEM
You can also try deleting the application and republishing it. If none of these things work, it may actually be an issue with the platform itself.
enabled HTTPS only under TLS/SSL settings in web app .
For me, it was because I didn't have the scope defined in my b2c configuration settings, like this:
"Resources": {
"myApi": {
"ResourceUri": "https://localhost:44361",//"https://my.ui.com",
"ResourceScopes": [
"https://myapp.onmicrosoft.com/my-api/Admin.Read.Write" // this was wrong, which caused my looping
]
}
}
I was also getting a logout redirect loop. It would actually log out, but just get stuck in a loop. In my case, the redirect URL I had configured in Azure was fine (I had /signin-oidc).
I followed the guide on adding my own account controller action rather than using the built in 'MicrosoftIdentity/Account/SignOut' (while also adding the 'id_token' validation to secure the logout): https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#secure-your-logout-redirect
My startup.cs code is per the documentation, my controller code looks like this (the documentation code is missing 'AuthenticationProperties' variable):
namespace Cosmos.WebPortal.Controllers;
[AllowAnonymous]
[Area("MicrosoftIdentity")]
[Route("[area]/[controller]/[action]")]
public class MyAccountController : Controller
{
[HttpGet("{scheme?}")]
public async Task<IActionResult> SignOutAsync([FromRoute] string scheme)
{
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
//obtain the id_token
var idToken = await HttpContext.GetTokenAsync("id_token");
//send the id_token value to the authentication middleware
properties.Items["id_token_hint"] = idToken;
return SignOut(properties, CookieAuthenticationDefaults.AuthenticationScheme, scheme);
}
}
So my logout link is now to this controller instead e.g. 'MicrosoftIdentity/MyAccount/SignOut'
That seems to work fine, no infinite loop. A bit frustrating as I don't really understand the cause or difference, but it works.
For me, it was an expired secret/certificate in Azure B2C. It's important to look at the network log to see if any message, thankfully there was message telling me exactly where to look

Microsoft Account Authentication - AuthenticationManager.GetExternalLoginInfoAsync() always null

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?

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:

Resources