Token validation stops working when using identity - asp.net

I've added token (jwt) validation to my Web-API by following the steps in this tutorial. However, when I now try to add Asp.Net Identity to my application it somehow skips the token validation.
The token generation still works fine.
The API-controller-action that I am trying to access looks like this:
// GET api/users/5
[HttpGet("{id}")]
[Authorize]
public User Get(int id)
{
return _userService.FindById(id);
}
The "Authorize" attribute doesn't seem to make any difference after adding identity to my OWIN startup file, which looks like this:
var secretKey = "secret";
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
var audience = "aud";
var issuer = "iss";
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
app.UseStaticFiles();
app.UseIdentity(); // When adding this the token-validation above seems to stop working...
var options = new TokenProviderOptions
{
Audience = audience,
Issuer = issuer,
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
};
app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));
app.UseMvc();
I've tried moving the Identity middleware above JwtBearerAuth, with no luck.
It always seems to skip validating my token, and even if I leave out the token I am able to reach the action, but when I remove the Identity middleware the token-validation starts working again.

Related

No authenticationScheme was specified issue

I am trying to validate token in multi tenant application. In startup(single tenant) earlier used this code for getting configuration data from appsettings.json.
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
But now we need to load configurations data from database. So i have added below code to startup.
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:3000";
options.Audience = "fcb78955-1a4a-6666-aa12-fc473b8fd8f6";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
});
and this is my token validation code. I have hardcode this for testing purpose. so please ignore it.
public static async Task<ClaimsPrincipal> TokentValidate(string token, string tenantId, string clientId)
{
try
{
var authorityEndpoint = "https://demo.identityserver.io/";
authorityEndpoint = "https://login.microsoftonline.com/" + "9111f39b-e5ed-8899-9c13-0005388e683a" + "/";
var openIdConfigurationEndpoint = $"{authorityEndpoint}.well-known/openid-configuration";
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(openIdConfigurationEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
clientId = "f0019d64-100a-4990-aa12-fc663b8899f6";
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidIssuer = openIdConfig.Issuer,
ValidAudiences = new[] { clientId },
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true
};
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(token, validationParameters, out validatedToken);
return user;
}
catch (Exception ex)
{
return null;
}
}
But this method not calling. So I have removed startup code. then i am getting this error.
{"Error":"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions)."}
I want to if we are using multi tenant and loading configurations from database what is the correct way. If anyone have an idea please help.

Bearer error="invalid_token", error_description="The signature is invalid"

I have a angular application that request a token from azure. The login went well and I get a token. This token is now send from the angular app to a net core webapi application. Net core should verify this token but failed. I think the webapi should also contact azure to validate the token because it has no knowledge of the private and public key that is needed to verify the token.
At the moment it is not clear why it is failing. Both angular app and the webapi are running local on my computer.
The error is: Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: 'IDX10500: Signature validation failed. No security keys were provided to validate the signature.'
my net core 2 config is:
var tokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = false,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidIssuer = "8d708afe-2966-40b7-918c-a39551625958",
ValidateAudience = true,
ValidAudience = "https://sts.windows.net/a1d50521-9687-4e4d-a76d-ddd53ab0c668/",
ValidateLifetime = false,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Audience = "8d708afe-2966-40b7-918c-a39551625958";
options.ClaimsIssuer = "https://sts.windows.net/a1d50521-9687-4e4d-a76d-ddd53ab0c668/";
options.RequireHttpsMetadata=false;
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
});
That is quite a lot of configuration you have :)
The two mandatory settings are the Audience and Authority:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Audience = "8d708afe-2966-40b7-918c-a39551625958";
o.Authority = "https://login.microsoftonline.com/a1d50521-9687-4e4d-a76d-ddd53ab0c668/";
});
You are missing the Authority so it does not know where to load the signing public keys from.
You are missing IssuerSigningKey property in your TokenValidationParameters. Thats why its complaining.
The simplest example would be
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yOURsECRETkEY12345"))
I'm not sure how azure comes into play, you probably need it to retrieve security key information, if thats your signing authority
Edit:
Azure specific settings
.AddJwtBearer(options => {
options.Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}/v2.0/", Configuration["Authentication:AzureAd:Tenant"], Configuration["Authentication:AzureAd:Policy"]);
options.Audience = Configuration["Authentication:AzureAd:ClientId"];
});

Having trouble with JWT token in ASP.NET Core app with multiple authentication schemes

I have an ASP.NET Core 2.0 app with both web pages and API. I'm using cookie authentication for web pages and now I want to use JWT Tokens for API methods.
I followed this article to set this up which does a pretty good job in walking us through the process: https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2
I am, however, getting a strange response in my API method that I set up to use JWT token. When I hit the API method, I do get a Status 200 response in Postman but I never hit the break point I set up in the API method. More interestingly, Visual Studio debugger is showing a successful request and when I click it, I see a response code 302 even though Postman shows me a 200 -- see below:
Stranger still, the response I see in Postman is the HTML code of my login page which I redirect users to if they're not authenticated.
Here's my configuration for authentication. As you'll see below, I use social logins as well so my configuration is a bit long so I put all the configuration in a separate file in order not to clutter my Startup.cs. I simply call this from the ConfigureServices() method.
public static void MyAppAuthenticationConfig(IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "my_cookie";
options.DefaultChallengeScheme = "my_cookie";
})
.AddCookie("my_cookie", options =>
{
options.AccessDeniedPath = "/Home/Denied";
options.LoginPath = "/Login";
})
.AddCookie("social_auth_cookie")
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_client_id";
options.ClientSecret = "my_secret";
options.CallbackPath = "/linkedin-callback";
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))";
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddFacebook(options =>
{
options.SignInScheme = "social_auth_cookie";
options.AppId = "my_app_id";
options.AppSecret = "my_secret";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketFacebookCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddGoogle(options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_id.apps.googleusercontent.com";
options.ClientSecret = "my_secret";
options.CallbackPath = "/google-callback";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketGoogleCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions =>
{
jwtBearerOptions.RequireHttpsMetadata = false;
jwtBearerOptions.SaveToken = true;
jwtBearerOptions.Challenge = JwtBearerDefaults.AuthenticationScheme;
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret_key")),
ValidateIssuer = true,
ValidIssuer = "myapp-api",
ValidateAudience = true,
ValidAudience = "myapp-client",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
}
And this is the API method where I want to use JWT Token.
[HttpGet("testit")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Test()
{
return Ok("Hello World!");
}
I have a break point where I return Ok("Hello World!"); but like I said I never hit it.
What am I doing wrong here?
UPDATE:
When I inspect the ChallangeResult("Bearer"), this is what I see:
And here's how I'm sending my request in Postman:

Get claims from JWT token in ASP.NET Core 2.0 API

I'm using mixed authentication in my ASP.NET Core 2.0 Web and API app. Meaning both cookies and now adding JWT token.
The web part of the app uses cookies and in the API part, I want to use JWT token.
My question is how do I get the claims from JWT token? In my web controllers, I can simply use HttpContext.User; to get the claims stored in a cookie. How do I handle it in my API methods where I want to use JWT token?
Here's my AuthenticationBuilder:
public static void MyAuthenticationConfig(IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "myApp_cookie";
options.DefaultChallengeScheme = "myApp_cookie";
})
.AddCookie("myApp_cookie", options =>
{
options.AccessDeniedPath = "/Unauthorized";
options.LoginPath = "/Login";
})
.AddCookie("social_auth_cookie")
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_client_id";
options.ClientSecret = "my_secret";
options.CallbackPath = "/linkedin-callback";
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))";
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddFacebook(options =>
{
options.SignInScheme = "social_auth_cookie";
options.AppId = "my_app_is";
options.AppSecret = "my_secret";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketFacebookCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddGoogle(options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_id.apps.googleusercontent.com";
options.ClientSecret = "my_secret";
options.CallbackPath = "/google-callback";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketGoogleCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret")),
ValidateIssuer = true,
ValidIssuer = "my-api",
ValidateAudience = true,
ValidAudience = "my-client",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
}
Normally the claims of JWT are automatically added to the ClaimsIdentity.
Source:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/af5e5c2b0100e8348c63e2d2bb45612e2080841e/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L1110).
So you should be able to just use 'User' property of the base 'Controller' class.
public async Task<IActionResult> Get()
{
// TODO Move 'Claims' extraction code to an extension method
var address = User.Claims.Where('GET THE NEEDED CLAIM');
...
}
I never had any problems getting the claims from a JWT token.
But I only used IdentityServer4.AccessTokenValidation so far. But internally it uses the Microsoft JWT Handler afaik.
Couldn't comment because my post would be to long, so making a seperate post instead.
If you follow the guide/link ErazerBrecht posted, the claims are indeed stored in the ClaimsPrincipal User. I created an extension method to retrieve the claim.
Note that I use an Enum for registering my claims. I use a dictionary to pass my claims to the method that generates my token, so my claim key should always be unique.
The extension method:
public static string GetClaim(this ClaimsPrincipal claimsPrincipal, JwtClaim jwtClaim)
{
var claim = claimsPrincipal.Claims.Where(c => c.Type == jwtClaim.ToString()).FirstOrDefault();
if (claim == null)
{
throw new JwtClaimNotFoundException(jwtClaim);
}
return claim.Value;
}
Call it like:
var userId = User.GetClaim(JwtClaim.UserId);

Keycloak client for ASP.NET Core

Is there any existing Keycloak client for Asp.net Core? I have found a NuGet package for .net but it doesn't work with Core. Do you have any ideas how to easily integrate with this security server (or maybe using any other alternatives)?
I've played a bit with this today. The most straightforward way is too use OpenId standard.
In Startup.cs I used OpenIdConnect Authentication:
public void Configure(...)
{ (...)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
CookieHttpOnly = true,
CookieSecure = CookieSecurePolicy.SameAsRequest
});
app.UseOpenIdConnectAuthentication(CreateKeycloakOpenIdConnectOptions());`(...)
}`
OpenIdConnectOptions method:
private OpenIdConnectOptions CreateKeycloakOpenIdConnectOptions()
{
var options = new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
Authority = Configuration["Authentication:KeycloakAuthentication:ServerAddress"]+"/auth/realms/"+ Configuration["Authentication:KeycloakAuthentication:Realm"],
RequireHttpsMetadata = false, //only in development
PostLogoutRedirectUri = Configuration["Authentication:KeycloakAuthentication:PostLogoutRedirectUri"],
ClientId = Configuration["Authentication:KeycloakAuthentication:ClientId"],
ClientSecret = Configuration["Authentication:KeycloakAuthentication:ClientSecret"],
ResponseType = OpenIdConnectResponseType.Code,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
};
options.Scope.Add("openid");
return options;
}
In appsettings.json add configuration for Keycloak:
{
(...),
"Authentication": {
"KeycloakAuthentication": {
"ServerAddress": "http://localhost:8180",
"Realm": "demo",
"PostLogoutRedirectUri": "http://localhost:57630/",
"ClientId": "KeycloakASPNETCore",
"ClientSecret": "secret-get-it-in-keycloakConsole-client-credentials"
}
}
}
Keycloak client is configuerd as followed:
Client settings,
I've added 'accounting' role for test,
I added mapper 'member_of' of type 'User Client Role' for roles so that roles are added in the claims
If I want to Authorize user by role I do something like this:
Add authorization by claims in ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
(...)
services.AddAuthorization(options =>
{
options.AddPolicy("Accounting", policy =>
policy.RequireClaim("member_of", "[accounting]")); //this claim value is an array. Any suggestions how to extract just single role? This still works.
});
}
I've edited get method in ValuesController (Default Web API template):
[Authorize(Policy = "Accounting")]
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public Dictionary<string,string> Get()
{
var userPrinciple = User as ClaimsPrincipal;
var claims = new Dictionary<string, string>();
foreach (var claim in userPrinciple.Claims)
{
var key = claim.Type;
var value = claim.Value;
claims.Add(key, value);
}
return claims;
}
If I login with user that has accounting role or is in group that has accounting role, it should display my user claims on address localhost:57630/api/values.
I hope this works for you.
Edit: .NET Core 2
Hi everyone! The way my app works changed quite a bit and I have not fully tested .NET Core 2 yet, but you can still try connecting to Keycloak like this in ConfigureServices:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = Configuration["Authentication:KeycloakAuthentication:ServerAddress"] + "/auth/realms/" + Configuration["Authentication:KeycloakAuthentication:Realm"];
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudiences = new string[] { "curl", "financeApplication", "accountingApplication", "swagger"}
};
options.RequireHttpsMetadata = false; //for test only!
options.SaveToken = true;
options.Validate();
});
And in Configure:
app.UseAuthentication();
You can access your token later with IHttpContextAccessor httpContextAccessor, for example:
public KeycloakAuthorizationRequirementHandler(IConfiguration config,
IHttpContextAccessor httpContextAccessor,
IMemoryCache memoryCache)
{
_config = config;
_httpContextAccessor = httpContextAccessor;
_memoryCache = memoryCache;
}
//get accessToken
var accessToken = _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
_httpContextAccessor.HttpContext.Items["username"] = username;
Tell me how it goes.
If you want to use standard .Net Role mappings with Keycloak Client Roles, setup like so:
Startup.cs:
services.AddAuthorization(options =>
{
options.AddPolicy("Users", policy =>
policy.RequireRole("Users"));
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["Authentication:oidc:Authority"]
options.ClientId = Configuration["Authentication:oidc:ClientId"];
options.ClientSecret = Configuration["Authentication:oidc:ClientSecret"];
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.RemoteSignOutPath = "/SignOut";
options.SignedOutRedirectUri = "Redirect-here";
options.ResponseType = "code";
});
appsettings.json:
"Authentication": {
"oidc": {
"Authority":"http://your-keycloak-server/auth/realms/your-realm",
"ClientId":"Your-Client-Name",
"ClientSecret":"Your-client-secret"
}
}
Keycloak Client Settings:
Create new Token Mapper
Mapper-Values (enter your own client name)
Now you can use standard authorize role statements to apply your Keycloak Client Roles to your ASP.NET project:
[Authorize(Roles = "Users")]
The thing that worked for us was setting these things in Startup.cs (it's cookie based authentication):
public void Configure(...)
{
(...)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
CookieHttpOnly = true,
CookieSecure = CookieSecurePolicy.SameAsRequest
});
app.UseOpenIdConnectAuthentication(CreateOpenIdConnectOptions(_customConfig));
(...)
}
And setting up the options:
private OpenIdConnectOptions CreateOpenIdConnectOptions(CustomConfigurationFile configuration)
{
var options = new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
Authority = configuration.ServerAddress + "/auth/realms/" + configuration.Realm,
RequireHttpsMetadata = true,
PostLogoutRedirectUri = configuration.SystemAddress,
ClientId = configuration.ClientId,
ClientSecret = configuration.ClientSecret,
ResponseType = OpenIdConnectResponseType.Code,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
};
options.Scope.Clear();
options.Scope.Add("openid");
return options;
}
Could we get an up-to-date answer on this question with .net core 5+? I've recently installed keycloak version 13.0.0 & its working allowing me to use single sign on with a couple apps. Now for the real reason I installed keycloak, for webapi authentication. Based on the answers above I've installed Microsoft.AspNetCore.Authentication.OpenIdConnect & have been working to get this to work on both the webapi side and on the client side.
For those who building KeyCloak-based authentication for .Net 6 backend and looking for JWT-token based solution here is a code to add in ConfigureServices of your app with Delobytes.AspNetCore.Infrastructure client:
services.AddKeyCloakAuthentication("SchemeName", true, options =>
{
options.Authority = "https://mykeycloakinstallation.com/auth/realms/myrealm"; //"issuer" endpoint
options.Audience = "account";
options.OpenIdConfigurationEndpoint = "https://mykeycloakinstallation.com/auth/realms/myrealm/.well-known/openid-configuration";
options.TokenValidationParameters = new TokenValidationOptions
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateIssuer = true,
ValidIssuer = "https://mykeycloakinstallation.com/auth/realms/myrealm",
ValidateAudience = true,
ValidAudience = "account",
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(2),
};
});

Resources