I am having issues with IdentityServer and I am learning it so there is a good chance I have something wrong. Basically I can run the App (.net core 5 app with identity server 4)
Below is my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.AddDbContext<BMProAppContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddInMemoryApiScopes(deviceclients.GetApiScopes())
.AddInMemoryClients(deviceclients.GetClients());
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerJwt();
}
Here is my Configure method:
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
When the app runs it issues a token successfully (I am using client credential flow)- then it throws an error as per below:
Token request success.
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: No authentication handler is registered for the scheme 'Bearer'. The registered schemes are: Identity.Application, Identity.External, Identity.TwoFactorRememberMe, Identity.TwoFactorUserId, idsrv, idsrv.external, IdentityServerJwt, IdentityServerJwtBearer. Did you forget to call AddAuthentication().AddSomeAuthHandler?
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
at IdentityServer4.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Not sure what is wrong and have searched for answer but nothing seems to help :(
Any advice appreciated.
You are declaring a "Bearer" scheme as the default authentication scheme, but you are forgetting the handler for that scheme.
If you use the defaultScheme parameter when you call AddAuthentication, you are setting the default scheme name and the application will try to use it when neccessary.
You need a registered handler with the same name to handle the request. Handlers also accept a scheme name when you register them. If you omit this parameter, an internal default value of each handler will be used, for example, jwtBearer internally uses "Bearer".
If you wanted to use the jwt bearer handler you'll need to use something like this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:5000/";
options.Authority = "https://localhost:5000/identity/";
})
But I guess you pretend to use the IdentityServerJwt handler instead.
Just call AddIdentityServerJwt without the scheme name parameter to add "IdentityServerJwt" as the scheme name for this handler. That is the default name assigned internally by the handler.
services.AddAuthentication()
.AddIdentityServerJwt();
You could call your scheme whatever you want by specifying the scheme name:
services.AddAuthentication()
.AddIdentityServerJwt("MySchemeName");
Be aware that your Identity service doesn't need this handler if you don't need to protect resources on it, that is, if you want that in addition to being the Identity service, it acts as an API resource protected by itself.
And another issue... according with IdentityServer4 documentation: UseIdentityServer includes a call to UseAuthentication, so it’s not necessary to have both.
When to add a default scheme in AddAuthentication(string defaultScheme)?
When you add a default scheme:
Any attempt to perform an authentication action (Authenticate, Forbid, Challenge, SignIn or SignOut) without specifying a scheme implicitly, will use the default scheme.
When a user is authenticated with more than one scheme, HttpContext.User will be set with the Identity resolved by the default scheme.
If it is not specified, we must use the Authorize attribute indicating the scheme or the schemes that should be used to construct the user's identity:
[Authorize(AuthenticationSchemes = "MyScheme")]
Related
I have my API, react spa app in one project, and am integrating asp.net identity into the same project. I have scaffolded it and everything works when I go to the identity pages. If I go to Identity/Account/Login I can register and login to a user just fine. However, when I attempt to log in from one of my pages, I get the following error after the login-callback when accessing https://localhost:44393/connect/token
System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'System.String', SecurityKey: 'Microsoft.IdentityModel.Tokens.X509SecurityKey'
is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures, Boolean cacheProvider)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm, Boolean cacheProvider)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.WriteToken(SecurityToken token)
at IdentityServer4.Services.DefaultTokenCreationService.CreateJwtAsync(JwtSecurityToken jwt)
at IdentityServer4.Services.DefaultTokenCreationService.CreateTokenAsync(Token token)
at IdentityServer4.Services.DefaultTokenService.CreateSecurityTokenAsync(Token token)
at IdentityServer4.ResponseHandling.TokenResponseGenerator.CreateAccessTokenAsync(ValidatedTokenRequest request)
at IdentityServer4.ResponseHandling.TokenResponseGenerator.ProcessAuthorizationCodeRequestAsync(TokenRequestValidationResult request)
at IdentityServer4.ResponseHandling.TokenResponseGenerator.ProcessAsync(TokenRequestValidationResult request)
at IdentityServer4.Endpoints.TokenEndpoint.ProcessTokenRequestAsync(HttpContext context)
at IdentityServer4.Endpoints.TokenEndpoint.ProcessAsync(HttpContext context)
at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
at IdentityServer4.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
I have wrapped my route in the default AuthorizeRoute componet like this :
<AuthorizeRoute path="/Settings" render={(props) => (<SettingsMenu {...props} cloud={this.state.cloud} evaluation={this.state.evaluation}/>)} />
and otherwise have not changed the code from the identity react pages.
I assume I am missing something in my configuration. But I built the sample identityreact project and that seems to work fine. Any pointers would be appreciated.
In the log it says
Algorithm: 'System.String',
That looks like a bug, what algorithm do you try to use?
The algorithm should be one of the one listed here
To create a signing key, you typically use the openssl command line tool to generate the signing key and also wrap it into a .pkcs12/.pfx file. If you use Azure Key Vault, it can generate it for you as well. Adding and importing the key into IdentityServer can be tricky.
I typically use this code to import the key into .NET:
private static SecurityKey LoadEcdsaKey()
{
var ecdsaCert = new X509Certificate2("es256.pfx", "mypassword");
SecurityKey ecdsaPrivateKey = new ECDsaSecurityKey(ecdsaCert.GetECDsaPrivateKey());
return ecdsaPrivateKey;
}
See this article on how to use openssl:
I have a GlobalExceptionHandler class which contain multiple methods annotated with #ExceptionHandler.
#ExceptionHandler({ AccessDeniedException.class })
public final ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
return new ResponseEntity<Object>(
"Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
}
I have a AOP which is suppose to be triggered after the exception handler returns response.
#AfterReturning(value="#annotation(exceptionHandler)",returning="response")
public void afterReturningAdvice(JoinPoint joinPoint, Object response) {
//do something
}
But the #AfterReturning is not triggered after the handler returns a valid response.
Tried full qualified name but not working
#AfterReturning(value = "#annotation(org.springframework.web.bind.annotation.ExceptionHandler)", returning = "response"){
public void afterReturningAdvice(JoinPoint joinPoint, Object response) {
//do something
}
Please go through the documentation to understand the proxying mechanisms in Spring framework.
Assuming the ExceptionHandler code written was of the following format
#ControllerAdvice
public class TestControllerAdvice {
#ExceptionHandler({ AccessDeniedException.class })
final public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
return new ResponseEntity<Object>(
"Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
}
}
key points from the documentation pertaining to the question are
Spring AOP uses either JDK dynamic proxies or CGLIB to create the
proxy for a given target object.
If the target object to be proxied implements at least one
interface, a JDK dynamic proxy is used. All of the interfaces
implemented by the target type are proxied. If the target object
does not implement any interfaces, a CGLIB proxy is created.
With CGLIB, final methods cannot be advised, as they cannot be overridden in runtime-generated subclasses.
OP identified the issue based on the comments and hints , this answer is for any future references.
I'm trying to get together with CXF's WS-security implementation(usernametoken). I've done everything as said at http://cxf.apache.org/docs/ws-security.html. My PasswordCallbackHandler seems to be working, but what bothers me is a part:
if (pc.getIdentifier().equals("joe")) {
// set the password on the callback. This will be compared to the
// password which was sent from the client.
pc.setPassword("password");
}
as said
Note that for up to and including CXF 2.3.x, the password validation of the special case of a plain-text password (or any other yet unknown password type) is delegated to the callback class, see org.apache.ws.security.processor.UsernameTokenProcessor#handleUsernameToken() method javadoc of the WSS4J project. In that case, the ServerPasswordCallback should be something like the following one:
so up to cxf 2.3.x it was done like that
if (pc.getIdentifer().equals("joe") {
if (!pc.getPassword().equals("password")) {
throw new IOException("wrong password");
}
}
My issue is: I don't want to pc.setPassword("plainTextPassword") as I want to store it in any resource. This up-to-2.3.x design would allow me to do this since I could encrypt it manually. Are there any ways of setting encrypted password in callback or doing usernametoken authentication for stored, encrypted passwords ?
I'm using cxf 2.5.x
The answer (which I've tried) is found in this blog page:
http://coheigea.blogspot.com/2011/06/custom-token-validation-in-apache-cxf.html
The essence is to create a subclass of org.apache.ws.security.validate.UsernameTokenValidator, and override the verifyPlaintextPassword method. In that method, the UsernameToken (which provides getName and getPassword) is passed. Throw an exception if they're not valid.
To install the custom validator in a spring configuration, add e.g.
<jaxws:properties>
<entry key="ws-security.ut.validator">
<bean class="com.example.webservice.MyCustomUsernameTokenValidator" />
</entry>
</jaxws:properties>
into the <jaxws:endpoint/>.
Callback Handlers are there to provide the plaintext password or verify a digest password where the plaintext password is known.
But if you don't know the plaintext i.e. its one way hashed, then the callback interface is not appropriate and you should create a class that implements the Validator interface.
Here is my example implementation of that interface that uses a JPA repository in which the password is already stored as a BCrypt hash.
Use with the ws-security.ut.validator property documented here
i.e. as a CXF property
<entry key="ws-security.ut.validator" value-ref="com.package.CustomUsernameTokenValidator" />
public class CustomUsernameTokenValidator implements Validator {
#Autowired
ProfileRepository profileRepository;
#Override
public Credential validate(Credential credential, RequestData requestData) throws WSSecurityException {
Profile profile = profileRepository.findByName(credential.getUsernametoken().getName());
if (profile != null) {
if (BCrypt.checkpw(credential.getUsernametoken().getPassword(), profile.getPassword())) {
return credential;
}
}
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
}
I'm using an AuthenticationService derived from AuthenticationBase in the standard business application template for RIA Services and using Forms authentication. I have my own CustomPrincipal that is created by my security infrastructure that I assign to Thread.CurrentPrincipal, so that it can be used by other service calls:
protected override bool ValidateUser(string userName, string password)
{
try
{
using (LoginService service = new LoginService())
{
SessionInfo info = service.Login(userName, password);
Thread.CurrentPrincipal = info.User;
SessionCache.Instance.Save(info);
}
}
catch (Exception e)
{
return false;
}
}
I've discovered, however, when other (non-authentication) RIA services are called, the Thread.CurrentPrincipal is reset to a GenericPrincipal object, so upon initialization of the other services, I override the Initialize() method of the domain service and set Thread.CurrentPrincipal by looking up the login in a session cache:
public override void Initialize(DomainServiceContext context)
{
base.Initialize(context);
if (context.User.Identity.IsAuthenticated)
{
SessionInfo info = SessionCache.Instance.FindByUsername(context.User.Identity.Name);
Thread.CurrentPrincipal = info.User;
}
}
Right now this lookup is being done by Username, because it is easily accessible in the GenericPrincipal, but I'd prefer that I could do the lookup via a session token. Is there a method with RIA services to easily persist a session token on the server during the lifetime of the session? I know I could put this session token in a cookie in the browser to persist it, but it seems like there should be a simpler method to persist a session token across the lifetime of the session. Perhaps the cookie method is the best way to do this, but I just wanted to confirm.
My question is similar to this question. I hope I can provide some more detail and context to get it answered.
So here's some context: I have a simple in-house silverlight (ver 4) app with WCF Ria services that I'm building for our small support team. It uses authentication against a third-party vended database, but all other user information, e.g. FriendlyName and Roles (only 1 role per user) comes from our own database. I'm trying to keep this simple and don't want to implement custom membership and role providers.
I have few domain service operations that I want to restrict to certain roles, so I tried using the RequiresRole attribute like so:
[RequiresRole("Admin", "HelpDesk", "Billing" )]
public RisStudyInfo GetStudyInfo(string accession) {
return ris.GetStudyInfo(accession);
}
On the client side WebContext.Current.User.IsInRole("Admin") returns true, but I always get access denied when calling the service. The RequiresAuthentication attribute works as expected.
Below is the implementation of my AuthenticationService. The User class simply inherits from UserBase and adds the FriendlyName property. Any ideas what I'm doing wrong?
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User> {
UserDataService userData = new UserDataService();
protected override bool ValidateUser(string userName, string password) {
var auth = new DatabaseAuthenticator();
return auth.Authenticate(userName, password);
}
protected override User GetAuthenticatedUser(IPrincipal principal) {
User user = null;
if (principal.Identity.IsAuthenticated) {
user = new User();
user.FriendlyName = userData.GetFriendlyName(principal.Identity.Name);
user.Name = principal.Identity.Name;
user.Roles = GetRolesFor(user.Name);
}
return user;
}
private IEnumerable<string> GetRolesFor(string username) {
IList<string> roles = new List<string>();
string role = userData.GetRolesFor(username);
if (role != null)
roles.Add(role);
return roles;
}
Figured it out. At least 2 things wrong. First clue found here. The second clue here
1.Turns out I really do need to write a custom role provider. Only need to implement GetRolesForUser though.
public override string[] GetRolesForUser(string username) {
return new string[] { _userService.GetRolesFor(username) };
}
2.Configure the custom role provider correctly in the web.config
<roleManager cacheRolesInCookie="true" enabled="true" defaultProvider="MyRoleProvider">
<providers>
<add name="MyRoleProvider" type="MyProject.Web.Providers.MyRoleProvider, MyProject.Web"/>
</providers>
</roleManager>
I solved this one by using the local credential store to cache credentials. Whenever a local cred check fails a foreign check occurs and the cache is populated/updated. This was a trivial override of the ValidateUser method. It does mean that stale passwords continue to work until the updated password is used (it will fail locally, pass remotely and trigger an update).
This approach meant that internally everything worked as per an out of the box configuration with no need for any other mods (apart from removing the local create-a-user links).