In my ASP .NET Core Web API, I'm manually generating a token for mocking purposes. However, it appears the token generator assumes the signature has a private key, while that's not the case. The key is public, retrieved from a JWKS. I don't know how to make this clear to the token generator. Can anyone help me?
This is my code:
private async Task<string> GenerateSecurityToken(string _expDate, string _issuer, string _audience, string _wellKnownEndpoint)
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(_wellKnownEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
SecurityKey securityKey = openIdConfig.SigningKeys.Last();
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(),
Expires = DateTime.UtcNow.AddMinutes(double.Parse(_expDate)),
Audience = _audience,
Issuer = _issuer,
SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); // Raises exception
return tokenHandler.WriteToken(token);
}
And this is the raised exception:
System.InvalidOperationException: IDX10638: Cannot create the SignatureProvider, 'key.HasPrivateKey' is false, cannot create signatures.
The identity provider use private key to encrypt token , and when validating token , you can get public key cryptography to sign tokens and verify that they're valid. So you can use a traditional way to get access token from identity provider using delegating flows , rather than use public key to issue access token manually .
Related
I am implementing a custom token endpoint for my identityserver4 project. The goal is to issue a token based on validation of a more complex credentials model (a separate user database than Identity Server's built in "client/scope" concept) and issue a Jwt token with extra claims added to help with user identity and access rights in my custom api.
My code is something like this:
[HttpPost]
public IActionResult GetCustomApiToken(CustomUserCredentialsModel credentials)
{
var customUser = GetCustomValidatedUser(credentials); //validate user from DB
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(ApplicationSettings.SigningKey); // <--- DeveloperSigningCredential ???
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("user", customUser.ToString()) /* extra custom claims */ }),
Issuer = "my identity server",
Audience = "my custom api",
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return Ok(tokenHandler.WriteToken(token));
}
Mind you I have not tested the above completely yet, but something like that should work in Production provided the key is managed in ApplicationSettings.
But it will not work in development where the signing key is added through Identity Server 4's AddDeveloperSigningCredential() extension.
One solution is to add SigningCredentials in configuration for all Dev/Test environements (= hassle).
Can I resolve the signing credential at runtime (as they are set in Program/Startup) ?
(Also, yes I know: don't store the signing keys readable in appSettings, please disregard that for the above example.)
Ok, so I figured it out, you can inject the ISigningCredentialStore singleton and resolve the signingCredential from there:
private readonly ISigningCredentialStore _signingCredentialStore;
public CustomTokenController(ISigningCredentialStore signingCredentialStore)
{
_signingCredentialStore = signingCredentialStore ?? throw new ArgumentNullException(nameof(signingCredentialStore));
}
[HttpPost]
public async Task<IActionResult> GetCustomApiToken(CustomUserCredentialsModel credentials)
{
var userId = GetCustomValidatedUser(credentials);
if (userId == null) return Unauthorized();
var signingCredentials = await _signingCredentialStore.GetSigningCredentialsAsync();
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("userId", userId.ToString()) /* extra custom claims */ }),
Issuer = "my IdentityServer",
IssuedAt = DateTime.UtcNow,
Audience = "my api",
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = signingCredentials
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return Ok(tokenHandler.WriteToken(token));
}
This worked for me and the Jwt token generated can be validated just like any token issued by the built in "connect/token" endpoint.
The following code below is used to authenticate users in ADFS 2016 and to request an Access Token for the resource defined in cp.APIBaseURL:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = cp.ClientId,
MetadataAddress = cp.MetadataAddress,
RedirectUri = cp.RedirectUri,
PostLogoutRedirectUri = cp.PostLogoutRedirectUri,
ResponseType = "code id_token",
Scope = "openid",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var cp = UnityConfig.Container.Resolve<IConfigurationProvider>();
AuthenticationContext ac = new AuthenticationContext(
configurationProvider.Authority, false,
new InMemoryTokenCache(context.AuthenticationTicket.Identity.Name));
AuthenticationResult ar = await ac.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(cp.RedirectUri),
new ClientCredential(cp.ClientId, cp.ClientSecretKey),
cp.APIBaseURL);
}
I would like to know how to change the code to request a 2nd Access Token for a different API (having a different audience)?
Can I also specify different scopes for the 2nd Access Token I need?
You can use result = await ac.AcquireTokenSilentAsync(resource, clientId); to request the access token for different resouces. Refer here for more details.
Can I also specify different scopes for the 2nd Access Token I need?
No,for v1(adal) Azure AD apps, scopes must be statically configured in the Azure Portal under the API permissions, configured permissions.
Not sure why my token is invalid. I fetch it with Postman calling login() and then pasting it into JWT.io it says "invalid signature"
I can paste my secret key into JWT.io and it will validate, but that doesn't seem right because it then changes the token to something different then what my login method returns to the user!
Here is my token and the secret key is "Super secret key". The token becaomes valid when pasted in JWT.io
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJjaHVjayIsInJvbGUiOlsiTWVtYmVyIiwiQWRtaW4iXSwibmJmIjoxNTc5MTU5NDQ0LCJleHAiOjE1NzkyNDU4NDQsImlhdCI6MTU3OTE1OTQ0NH0.Uau2W66y7Kdj01MQbBeoOXiwVzQJSDMEZnbQc2jt1qUyfdc9N5bsla1VGMPHQPjRAcnKSfY3NwQBhCRE-SHZCQ
Here is where I paste my secret key to validate the token
Then I create a new Postman get query to fetch some values and I use the token sent back from the login. But I get 401 Unauthorized.
I added the token (same one that the login sent me not the changed one after I validated it) in postman like this
I also added "IdentityModelEventSource.ShowPII = true;" to the startup file to show additional debugging and here' what I see in the debug console.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler1
Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId:
'.
Exceptions caught:
''.
token: '{"alg":"HS512","typ":"JWT"}.{"nameid":"1","unique_name":"chuck","role":["Member","Admin"],"nbf":1579159444,"exp":1579245844,"iat":1579159444}'.
I can paste my token generation code if asked.
Any explanation in understanding what's going on here would be much appreciated.
Questions - did I miss a step? Do I provide the same token the login sends me back to the authorized controller method or the changed token (tried both)?
Here is the code I use to create the token.
private async Task<string> GenerateJwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName)
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8
.GetBytes(_config.GetSection("AppSettings:Token").Value));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
I added
app.Authentication()
to startup and it worked...
I'm trying to implement ROPC flow in asp.net api . Can you please guide me on how to achieve this properly in ASP.net core using built in authentication library. If it is not possible what are the alternatives
I'm able to get the access token using http call to azure AD token endpoint. But i'm not able to validate the token using built in authentication library
can we implement this without the HTTP call , but using built in library.
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
Im using the below code snippet to get the access token
private async Task<string> GetTokenAsync(string username, string password)
{
string grantType = "password";
string tenantId = Configuration["AzureAdNative:TenantId"];
string clientId = Configuration["AzureAdNative:ClientId"];
string resource = Configuration["AzureAdNative:Resource"];
string endpoint = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var dict = new Dictionary<string, string>();
dict.Add("grant_type", grantType);
dict.Add("client_id", clientId);
dict.Add("username", username);
dict.Add("password", password);
dict.Add("resource", resource);
var req = new HttpRequestMessage(HttpMethod.Post, endpoint) { Content = new FormUrlEncodedContent(dict) };
var res = await client.SendAsync(req);
string result = res.Content.ReadAsStringAsync().Result;
var jwt = JObject.Parse(result);
return result;
// return jwt.GetValue("access_token").ToString();
}
getting unauthorised 401 error while validating with above code. access token is being sent in the authorization header (eg: Authorization : Bearer e4dgkddskdsdk).
I am in the process of splitting up my asp.net service to multiple micro services. As a process, I have created my identity service using Node.Js and it uses JWT for tokens.
Now i want to use this token in C# so that all my [Authorise] attributes use this token and allow access.
I have looked at many implementations, but could not get this to work. Since JWT is a standard impementation, i do not understand a reason why this would not work.
This is my C# code
public void ConfigureAuth(IAppBuilder app)
{
var issuer = "myorg/identity2";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode
("xfecrrt7CV");
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
However, I get this error everytime i try to access a protected method.
{"Message":"Authorization has been denied for this request."}
Is there anything i am missing here? How do i add the claim identity to this?
Finally, it was resolved. One of my friends debugged the Identity source code and recommended to increased the key length. After increasing the key length, I was able to validate the token