IdentityServer4 error when using WindowsAuthentication - asp.net-core

I successfully followed the Deblokt tutorial on IdentityServer4 (https://deblokt.com/2020/01/24/01-identityserver4-quickstart-net-core-3-1/). This tutorial uses the IdentityServer4 QuickstartUI. Userid/password authentication using the AspNet Identity database is working.
I'm using the latest .Net Core (3.1), which differs slightly from the tutorial, but it is working.
Now I'm attempting to add Windows Authentication and hitting a runtime error. What befuddles me is that the error is occurring after the user is successfully authenticated by windows. I'm running locally on a Windows 10 machine inside Visual Studio 2019 in debug mode, using Google Chrome.
Per the IdentityServer4 documentation (https://identityserver4.readthedocs.io/en/latest/topics/windows.html#using-kestrel) I added this to my Startup.cs:
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
I then right-clicked the project, chose "Properties", chose the "Debug" tab, and checked "Windows Authentication":
This makes the "Windows" button visible when I navigate to /Account/Login:
When the "Windows" button is clicked the postback is handled inside the ExternalController "Challenge" method, with "Windows" as the provider argument and "~/" for the returnUrl argument. Since the provider argument is "Windows" this method calls ProcessWindowsLoginAsync, passing the returnUrl argument.
The first time, ProcessWindowsLoginAsync calls HttpContext.AuthenticateAsync("Windows"), which returns a null Principal, which causes the method to re-call /External/Challenge("Windows"), which calls ProcessWindowsLoginAsync a second time, which in turn calls HttpContext.AuthenticateAsync("Windows"), and this 2nd call is successful--HttpContext.AuthenticateAsync returns a windows principal (which is me) and I can view it in the debugger and see that it has all of my particulars correct.
ProcessWindowsLoginAsync then makes this call:
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props
);
This executes without error. The "id" argument is a ClaimsIdentity with 2 claims pulled from my windows identity, Subject and Name.
At this point all appears to be well. The method ProcessWindowsLoginAsync ends with a Redirect to /External/Callback. This is where the error occurs. The method /External/Callback method contains this bit of code at the top, and is throwing the exception you see. For reasons unknown HttpContext.AuthenticateAsync is not succeeding.
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
The "result" object has a "Succeeded" value of false and a "None" value of true, the other properties (Principal, Properties, and Ticket) are all null.
Any advice is appreciated.

I had a similar problem, IdentityServer code threw "External authentication error" for Google login
[HttpGet]
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
I had to add the external SignInScheme (IdentityServerConstants.ExternalCookieAuthenticationScheme) in my Startup.cs:
services.AddAuthentication()
.AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "my_client_id";
options.ClientSecret = "my_client_secret";
});

The fix is to change this, on ExternalController.cs line 173:
await HttpContext.SignInAsync(
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
To this:
await HttpContext.SignInAsync(
IdentityConstants.ExternalScheme,
new ClaimsPrincipal(id),
props);
This seems obvious in hindsight: AuthenticateAsync and SignInAsync should reference the same scheme.
After bumbling across the solution I notice that the IdentityServer4 github already has a closed bug report from March 2018, I don't know why this bug is still part of the Quickstart though: https://github.com/IdentityServer/IdentityServer4/issues/2166

Related

Windows authentication fail with "401 Unauthorized"

I have a MVC client accessing a Web API protected by IDS4. They all run on my local machine and hosted by IIS. The app works fine when using local identity for authentication. But when I try to use Windows authentication, I keep getting "401 Unauthorized" error from the dev tool and the login box keeps coming back to the browser.
Here is the Windows Authentication IIS setting
and enabled providers
It's almost like that the user ID or password was wrong, but that's nearly impossible because that's the domain user ID and password I use for logging into the system all the time. Besides, according to my reading, Windows Authentication is supposed to be "automatic", which means I will be authenticated silently without a login box in the first place.
Update
I enabled the IIS request tracing and here is the result from the log:
As you can see from the trace log item #29, the authentication (with the user ID I typed in, "DOM\Jack.Backer") was successful. However, some authorization item (#48) failed after that. And here is the detail of the failed item:
What's interesting is that the ErrorCode says that the operation (whatever it is) completed successfully, but still I received a warning with a HttpStatus=401 and a HttpReason=Unauthorized. Apparently, this is what failed my Windows Authentication. But what is this authorization about and how do I fix it?
In case anyone interested - I finally figured this one out. It is because the code that I downloaded from IndentityServer4's quickstart site in late 2020 doesn't have some of the important pieces needed for Windows authentication. Here is what I had to add to the Challenge function of the ExternalController class
and here is the ProcessWindowsLoginAsync function
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action(nameof(Callback)),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props);
return Redirect(props.RedirectUri);
}
else
{
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
}
Now my windows authentication works with no issues.

Azure AD login inifinite loop

My code is entering an infinite loop, hitting azure login page (hosted by Microsoft), then redirecting back to my app, then back to ms host login page etc etc etc.
In my code I have a breakpoint in the OnAuthorizationCodeReceived event...
public void ConfigureAzureAd(IServiceCollection services)
{
//set authentication to use Azure AD
services.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
//Get token for Microsoft Graph API using the authorization code
string resource = "https://graph.microsoft.com";
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
//ctx.HandleCodeRedemption();
}
};
});
}
and I can inspect the data in result, and it all looks ok (though not sure what failure would look like), it appears the login is working but my app is unable to recognize that the login has happened, or it's not saving, and keeps retrying
I've also asked someone else to try logging in with a user not in my Active Directory, and it fails appropriately, it really looks like Active Directory is happy, but my app just keeps redirecting.
I'm using .Net Core 2.2 (my first core project)
I'm using Active Directory Free
Update in response to #Marilee Turscak - MSFT
If i do not have the correct Reply Url setup in portal.azure.com and pass in it via C# then azure throws an error, so I've definitely got a reply URL in there and it matches correctly
Config looks like this:
"OpenIdConnect": {
"ClientId": "<guid in here>", // Application ID
"ClientSecret": "<secrect from portal.azure.com>",
"Authority": "https://login.microsoftonline.com/emailwithout#symbol.onmicrosoft.com/",
"PostLogoutRedirectUri": "http://www.<projectname_in_here>.local",
"CallbackPath": "/signin-oidc",
"ResponseType": "code id_token"
}
You need to set a Reply URL both in your code and in your application registration in Azure AD. You should set your Reply URL to wherever you want the user to be redirected (generally your main published homepage url - like https://myapp.azurewebsites.net).
For reference, you can see the examples in the Github samples.
https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-openidconnect/
Answering my own question...
I think my issue was somehow related to feature folders. I was implementing custom routing to enable feature folders, but the Azure AD code sets it's own custom route to "/signin-oidc". I came to this conclusion by using Visual Studio to create a new project with Azure Active Directory wizard, got the test project signing-in, but when I ported my old code to the new test project, I got exactly the same error, but in the new "Visual Studio Wizard" there was very little configuration, AND it interfaced with my Azure AD and registered the app and added all the required configuration, so i knew it would before adding the feature folders, and produced exactly the same error behavior after feature folders, so conclude it was something to do with the feature folders custom routing.
Url to the code I found to help implement feature folders if anyone is interested: https://github.com/ardalis/OrganizingAspNetCore/tree/master/CoreFeatureFolders

Getting access token within Claims Transformer in ASP.NET Core

I'm developing a set of applications including an Identity Server using IdentityServer4, a .NET Core MVC app, a .NET Core WebAPI.
As part of a asp.net core mvc application I am using AddOpenIdConnect to do authentication and doing options.SaveTokens = true.
However, as part of Claims Transformation, in TransformAsync I would like to be able to have access to the access token provided by the identityserver. This is to be able to call a permissions endpoint on the api to populate the principal with claims that I can use to do authorization on my controllers etc.
If I call HttpContext.GetTokenAsync("access_token") I get a stackoverflowexception due to the infinite loop created by authenticate being called, which then calls TransformAsync again.
Is this a sound approach in the first place? Typically, TransformAsync is where I would populate application permissions. Is there any way of accessing the token without triggering the authenticate again?
Would appreciate any help as we're a bit stumped! Thanks
Edit: I've seen suggestions around doing transformations in the OnTicketReceived
event. It looks like I'd have access to the token through the properties in there. Is this a better place to do it?
I came across the same problem. My solution was,
Override JwtBearerEvents.TokenValidated event called by IdentityServer4.AccessTokenValidation middleware.
private Task OnTokenValidated(TokenValidatedContext tokenValidatedContext)
{
tokenValidatedContext.HttpContext.Items["access_token"] = (tokenValidatedContext.SecurityToken as JwtSecurityToken).RawData;
return Task.CompletedTask;
}
This will utilize HttpContext.Items collection which is request scoped. Now you can retreive this access token in TransformAsync method, like below.
var access_token = _httpContextAccessor.HttpContext.Items["access_token"] as string;
Please note that you need to inject IHttpContextAccessor to access HttpContext in ClaimsTransformer.
It has been many years since this question was posted, but if you are still looking for a solution to the issue, you can get the access token in the OnTokenValidated event.
OnTokenValidated = tokenValidatedContext =>
{
var handler = new JwtSecurityTokenHandler();
// get access token
var jsonToken = handler.ReadJwtToken(tokenValidatedContext.TokenEndpointResponse.AccessToken);
var claims = new List<Claim>();
claims.Add(new Claim("customClaimType", "customClaimValue"));
var appIdentity = new ClaimsIdentity(claims);
tokenValidatedContext.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
}
Reference : Adding Custom Claims During Authentication
I think you can inject the IAuthenticationHandlerProvider service and use following:
Get the authentication handler by scheme name.
get the AuthenticateResult by invoking AuthenticateAsync
get the token from the authentication properties
var token = string.Empty;
var handler = await Handlers.GetHandlerAsync(context, scheme); // i.e. "OIDC"
var result = await handler.AuthenticateAsync();
if(result?.Succeeded == true) {
token = result?.Properties?.GetTokenValue(tokenName);
}
haven't tested it but i think it should work

ASP.NET Core Entity Framework User Impersonation Failing

I'm developing an ASP.NET Core webapp that requires forwarding the user's window's credentials to use for database access through Entity Framework. But an exception is thrown on database access. System.Data.SqlClient.SqlException: 'login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.'.
I have tried using impersonation in middleware, as well as around the query itself. The way I invoke it is:
using (WindowsImpersonationContext impersonationContext = ((WindowsIdentity)User.Identity).Impersonate())
{
var debug2 = WindowsIdentity.GetCurrent(); // shows the desired user
entiryFrameworkContext.BlahBlahBlah.ToList();
}
Or if I'm taking the middleware approach:
app.Use(async (context, next) =>
{
using (WindowsImpersonationContext impersonationContext = ((WindowsIdentity)context.User.Identity).Impersonate())
{
var debug = WindowsIdentity.GetCurrent(); // shows the desired user
await next.Invoke();
}
});
The results are the same whether in release mode pushed to IIS or running in VS2017 IIS Express.
Does anyone have an idea why the anonymous user is still used for db calls? And if so, how do I fix it?

.NET CORE API Making Facebook Login Work With Openiddict/Identity

I have one project (Project A) which is a .NET CORE API project using Openiddict with an endpoint of /connect/token to issue JWT tokens using Identity to handle the security etc. This project works great as is.
I have another project (Project B), which is just a very simple project with some HTML that makes requests to the API to get an access token, and get data from the API. This project also works great.
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project? When connecting with Facebook, what redirect uri should I use (API/HTML project)? Then who should have the below code in their 'Startup.cs' file?
app.UseFacebookAuthentication(new FacebookOptions
{
AppId = "xxxxxxx",
AppSecret = "xxxxxxxxx",
Scope = { "email", "user_friends" },
Fields = { "name", "email" },
SaveTokens = true,
});
And finally if this helps here is how I have Project A currently setup:
STARTUP.CS (API)
public void ConfigureServices function: (API)
// add entity framework using the config connection string
services.AddEntityFrameworkSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// add identity
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// add OpenIddict
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens()
.AddEphemeralSigningKey();
services.AddCors();
public void Configure function: (API)
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:54418/",
Authority = "http://localhost:54418/"
});
Authorization Controller (API)
public class AuthorizationController : Controller
{
private OpenIddictUserManager<ApplicationUser> _userManager;
public AuthorizationController(OpenIddictUserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIdConnectRequest();
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
ErrorDescription = "The username or password provided is incorrect"
});
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// Add a custom claim that will be persisted
// in both the access and the identity tokens.
if (user.Avatar != null)
{
identity.AddClaim("user_avatar", user.Avatar,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
if (user.InSiteUserName != null)
{
identity.AddClaim("insite_username", user.InSiteUserName,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
identity.AddClaim("hasLoggedIn", user.HasLoggedIn.ToString(),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
}
}
I don't know if it's including anything from Project B since it's pretty basic/bare and relies on the API for everything.
I know this is a loaded and complicated question, and I'm sure I'm not presenting it as fluidly as possible so I apologize in advance for that, like I said before, I'm confused. Thank you!
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project?
In the recommended case (i.e when using an interactive flow like the authorization code flow or the implicit flow), the authorization server project itself is responsible of handling the external authentication dance, using the social providers you've configured in your ASP.NET Core pipeline.
In theory, the final client application (i.e the JS app) doesn't even know that you've decided to use external authentication at the authorization server level, since it's not directly linked to Facebook or Google.
In this case, the redirect_uri configured in the Facebook options must correspond to an endpoint owned by the authorization server application (in your case, it's provided by the Facebook authentication middleware).
If you don't like this approach, there's also a different flow named "assertion grant", that basically reverses how things are handled: the final client app (the JS app in your case) is directly linked to Facebook - so the redirect_uri must correspond to the JS app - and uses OpenIddict's token endpoint to "exchange" Facebook tokens with tokens issued by your own server, that can be used with your own APIs.
For more information about this flow, please read Exchanging a google idToken for local openId token c#.