I have an IdentityServer 4 server set up as a stand-alone app, using net core 3.1, Entity Framework core against MySql and Net Core Identity as a user store. Separately I have a Razor Pages client app, which authenticates against the Identity Server, with user logon taking place on the server. All this is working fine.
I now wish to be able to write a log entry on the client for any new user authentication or failed logon. I assume there must be events raised somewhere. How do I go about this, please?
I had the same requirement, to store for example authentication failure in audit log - if someone tried to access api with invalid token.
What I found is that IdentityServerAuthenticationOptions has JwtBearerEvents property which can be used to be notified about such events.
In my case it worked this way:
.AddAuthentication(AUTH_SCHEME).AddIdentityServerAuthentication(AUTH_SCHEME, options => {
options.ApiName = config.ApiName;
options.Authority = config.Address;
options.JwtBearerEvents = new JwtBearerEvents
{
OnAuthenticationFailed = c =>
{
// Log can bo done here
return Task.CompletedTask;
},
OnTokenValidated = c =>
{
// Log can bo done here
return Task.CompletedTask;
},
OnChallenge = c =>
{
// Log can bo done here
return Task.CompletedTask;
},
OnForbidden = c =>
{
// Log can bo done here
return Task.CompletedTask;
}
};
options.SupportedTokens = SupportedTokens.Jwt;});
I was afraid that it will override normal IS behavior, but everything seems to work as it used to.
You have access to proper contexts there, for example in JwtBearerChallengeContext you can read Error or ErrorDescription strings or even whole HttpContext.
Related
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.
It seems to set up OpenIdConnect authentication from .NET Core 2.2 to IdentityServer3 I have to setup through generic AddOpenIdConnect() call, and in order for scope policy to work, I have overridden OnTokenValidated, where I parse the access token received, and add the scopes in it to the ClaimsPrincipal object.
I have found no other way of getting scope policy to work. This seems a bit hackish though. Is there a better or simpler way, so I don't need to override events, or at least not parse the access token? It is parsed in the framework anyhow, so I would suspect there were other functionality available to get scopes into the claims principal.
Moving our code from .NET 4.5.2 to .NET Core 2.2, I need to set up authentication towards our IdentityServer3 server in a very different way.
I was hoping new functionality in later framework allowed for simple setup of authentication towards IdentityServer3, but I've found no fitting example.
I saw someone saying that IdentityServer4.AccessTokenValidation NuGet package could work towards IdentityServer3, but only example I've found has been with simple JWT authentication not allowing implicit user login flow.
Consequently, I've ended up using standard ASP.NET Core libraries to set up openidconnect, and then I need to tweak the code to make it work.
Not sure if the code below handles all it needs to, but at least I've gotten where I can log in and use the new web site, and write cypress tests. Any suggestions on how to do this better or simpler would be appreciated.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
// Without this, I get "Correlation failed." error from Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(o => {
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect(o =>
{
o.Authority = "https://myidentityserver3.myfirm.com";
o.ClientId = "myidentityserver3clientname";
o.SignedOutRedirectUri = "https://localhost:50011/signout";
o.ResponseType = "id_token token";
o.SaveTokens = true;
o.Scope.Add("openid");
o.Scope.Add("roles");
o.Scope.Add("profile");
o.Scope.Add("customrequiredscopeforapi");
o.GetClaimsFromUserInfoEndpoint = false;
{
var old = o.Events.OnTokenValidated;
o.Events.OnTokenValidated = async ctx =>
{
if (old != null) await old(ctx);
var token = MyCustomAuthUtils.ParseBearerToken(ctx.ProtocolMessage.AccessToken);
foreach (var scope in token.Scopes)
{
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("Scope", scope) }));
}
// Our controllers need access token to call other web api's, so putting it here.
// Not sure if that is a good way to do it.
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("access_token", ctx.ProtocolMessage.AccessToken) }));
};
}
});
var mvcBuilder = services.AddMvc(o =>
{
o.Filters.Add(new AuthorizeFilter(ScopePolicy.Create("customrequiredscopeforapi")));
});
services.AddAuthorization();
}
The first thing is you don't need to manally decode the access token , just use ctx.SecurityToken.Claims in OnTokenValidated event to get all claims included in the token .
I'm not sure why you need to use scope to identify the permission . The scope parameter in the OIDC-conformant pipeline determines:
The permissions that an authorized application should have for a given resource server
Which standard profile claims should be included in the ID Token (if the user consents to provide this information to the application)
You can use role to identify whether current login user could access the protected resource . And the OpenID Connect middleware will help mapping the role claim to claim principle .
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?
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#.
I totally understand if someone finds that my question is very basic or might not make a lot of sense all the way.
I am new to this and I am trying to use the latest .NET Framework 5 with MVC 6 in order to build a Web Api that could be used from an Angular JS client-side. This will allow me to create a website for it, as well as a mobile application by wrapping it with Phonegap. So please bear with me a bit.
What I am trying to achieve for the moment is to have a Web API controller that receives a login request and returns a result to the client based on Cookie Authentication (later the client should store this cookie and use it for communications with the server)
I added the following in the project.json
In the Startup.cs, I added under ConfigureServices:
// Add entity framework support
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]);
});
// add ASP.NET Identity
services.AddIdentity<ApplicationUser, IdentityRole>(options => {
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonLetterOrDigit = false;
options.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
In the Startup.cs, under Configure:
// Using the identity that technically should be calling the UseCookieAuthentication
app.UseIdentity();
Now, in the Controller method to login, I am able to find the user using its email address and the UserManager:
// Verify that the model is valid according to the validation rules in the model itself.
// If it isn't valid, return a 400 Bad Request with some JSON reviewing the errors
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
// Find the user in our database. If the user does not exist, then return a 400 Bad Request with a general error.
var user = await userManager.FindByEmailAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", INVALID_LOGIN_MESSAGE);
return HttpBadRequest(ModelState);
}
// If the user has not confirmed his/her email address, then return a 400 Bad Request with a request to activate the account.
if (!user.EmailConfirmed)
{
ModelState.AddModelError("Email", "Account not activated");
return HttpBadRequest(ModelState);
}
// Authenticate the user with the Sign-In Manager
var result = await signInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
// If the authentication failed, add the same error that we add when we can't find the user
// (so you can't tell the difference between a bad username and a bad password) and return a 400 Bad Request
if (!result.Succeeded)
{
ModelState.AddModelError("", INVALID_LOGIN_MESSAGE);
return new BadRequestObjectResult(ModelState);
}
return Ok();
The problem is happening at the line:
// Authenticate the user with the Sign-In Manager
var result = await signInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
it is throwing the following error:
Error: No authentication handler is configured to handle the scheme:
Microsoft.AspNet.Identity.Application
I am currently blocked and I searched googled for almost every possible token I could think of and tried multiple solution still in no vain. Any help is highly appreciated.
Regards,
Ok I finally figured it out after writing this whole question and I wanted to share the answer to avoid the hussle for someone else if they commit the same mistake I did!
The problem was that in the Configure in Startup.cs, I called "app.UseIdentity()" after calling "app.UseMVC()". The order should have been inversed. I donno if this is common knowledge or I should have read about it somewhere.