I'm trying to convert my token string to jwt token using JwtSecurityTokenHandler. But it's getting error that saying
IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII is hidden]'.\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
How can I solve this issue?
Here is my token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwibmJmIjoxNTUwNjM3NzcxLCJleHAiOjE1NTA2Mzg5NzEsImlhdCI6MTU1MDYzNzc3MX0.tUcoyoHgkrX3rDKl0cRLd9FwLtRprQpgYepMoiekixY
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
Calling web api
using (HttpClient client = new HttpClient())
{
string path = "UserMaintenance/ValidateUserId?userid=" + txtUsername.Text.Trim().ToString();
client.BaseAddress = new Uri(GlobalData.BaseUri);
client.DefaultRequestHeaders.Add("Authorization", "Bearer" + GlobalData.Token);
HttpResponseMessage response = client.GetAsync(path).Result;
if (response.IsSuccessStatusCode)
{
var value = response.Content.ReadAsStringAsync().Result;
isValid = JsonConvert.DeserializeObject<bool>(value);
}
}
Here is my GetPrincipal method
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken securityToken;
var principal = handler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception ex)
{
return null;
}
}
This is how I do it and it works for me:
var token = new System.IdentityModel.Tokens.JwtSecurityToken(jwt);
The above line works for System.IdentityModel.Tokens.Jwt package version 4.0.0.
As #Nick commented, in the latest versions of the package, the JwtSecurityToken does not exist in the previous namespace anymore, instead it exists in System.IdentityModel.Tokens.Jwt so you need to write:
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(jwt);
Unless your token is not well-formed. It would be better if you share the token too.
Update:
You also need to remove the word "Bearer " from the beginning of the token (If you haven't):
var jwt = context.Request.Headers["Authorization"].Replace("Bearer ", string.Empty);
at version 5.6.0.0 - currently is the latest version
can use similar code as in #thilim9's question.
var tokenId = identity.Claims.SingleOrDefault(c => c.Type == "id_token")?.Value;
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.ReadJwtToken(tokenId);
For .net framework 4.5.1 I remove my custom key while generating token and use default values in claims of JwtRegisteredClaimNames.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.GivenName, Data.UserName),
new Claim(JwtRegisteredClaimNames.Prn,Data.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
Related
I have created a .net core console application to access the graph api. I created a authentication by using clientId and clientSecret of the Azure AD application
string tenantName = "MY.TENANT";
string authUrl = "https://login.microsoftonline.com/" + tenantName;
var clientId = "MYID";
var clientSecret = "MYSECRET";
AuthenticationContext authenticationContext = new AuthenticationContext(authUrl, false);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult;
authenticationResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com/", clientCred);
return authenticationResult.AccessToken;
After I get a valid token the call do a sharepoint list works fine and I get some data
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"{graphUrl}/sites/{siteId}/lists/MYLISTGUID/items?expand=fields");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
return responseString;
}
But if I call the Search API I get the following error: SearchRequest Invalid (Region is required when request with application permission.)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"{graphUrl}/search/query/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
}
}
}
};
request.Content = new StringContent(JsonConvert.SerializeObject(filter), Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
}
The same query by using the Graph Explorer works fine.
I found some posts around that tells something, that you can not call the search API by using the application credential but only by using delegation. In my case the api call is made by a service user and not by the user directly. I have to migrate a Sharepoint on Premise solution which access the search in that way.
Thanks for any input
You can get the region value by calling the following URL
https://yourtenant.sharepoint.com/_api/GeoTenantInstanceInformationCollection
Note: your tenant admin needs to call (copy&paste in the browser) this URL otherwise you will receive UnauthorizedAccessException with the message Current user is not a tenant administrator.
Then add region property with the value from the request above to your filter:
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
},
Region = "guid"
}
}
};
Resources:
Search content with application permissions
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 .NET Core method returns BadRequest error:invalid_grant
However not always, only in the middle of a session - not sure what else is needed. The request is made from a Blazor App:
private async Task<TokenResponse> RefreshAccessToken()
{
string authority = _configuration.GetValue("Authority", "url...");
using (HttpClient serverClient = _httpClientFactory.CreateClient())
{
var discoveryDocument = await serverClient.GetDiscoveryDocumentAsync(authority);
var refreshToken = _tokenProvider.RefreshToken;
using (HttpClient refreshTokenClient = _httpClientFactory.CreateClient())
{
TokenResponse tokenResponse = await refreshTokenClient.RequestRefreshTokenAsync(
new RefreshTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
RefreshToken = refreshToken,
ClientId = "client id ...",
ClientSecret = "secret ..."
});
return tokenResponse;
}
}
}
This is the request message:
Iam working on app, which consists from angular frontend and ASP net Web API backend(.net 4.5). For authentication iam using OpenIdConnect. I succesfully connected frontend to identity provider but now i need to validate id token on backend, so i can be sure, that only validated users can call backend.
This id token use rs256 algorithm for signing. So on backend, i need to do two things:
Get JWKs from identity provider URL - iam a little lost here, should i get it throug normal HttpClient, or there is some library or helper function to do this?
Generate RSA public key out of JWKs and validate token - for this iam using this function:
string token="xyz..";
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(
new RSAParameters()
{
Modulus = FromBase64Url("xyz.."),
Exponent = FromBase64Url("xyz..")
});
var validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = true,
IssuerSigningKey = new RsaSecurityKey(rsa)
};
SecurityToken validatedSecurityToken = null;
var handler = new JwtSecurityTokenHandler();
handler.ValidateToken(tokenStr, validationParameters, out validatedSecurityToken);
JwtSecurityToken validatedJwt = validatedSecurityToken as JwtSecurityToken;
It works, but now i need to connect it somehow with the loaded JWKs and register it to use this for every request that comes. Any advices or simple example would really help me. Thx.
This code below is taken from one of my training classes and it will automatically download and validate the provided token and I hope you can use it as a reference. You typically will use the ConfigurationManager to download the IdentityServer configuration and JWKS for you. It will also internally cache and periodically (every 24) readload the config.
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using OpenID_Connect_client.Models;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
namespace OpenID_Connect_client
{
public class TokenValidator
{
private readonly IOpenIDSettings openIDSettings;
public TokenValidator(IOpenIDSettings openIDSettings)
{
this.openIDSettings = openIDSettings;
}
public string ValidateToken(string token, string clientId)
{
try
{
string issuer = openIDSettings.Issuer;
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{issuer}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result;
// Configure the TokenValidationParameters. Assign the SigningKeys which were downloaded from Auth0.
// Also set the Issuer and Audience(s) to validate
//https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
var validationParameters =
new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.SigningKeys,
ValidAudiences = new[] { clientId },
ValidIssuer = issuer,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateTokenReplay = true
};
// Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(token, validationParameters, out validatedToken);
// The ValidateToken method above will return a ClaimsPrincipal. Get the user ID from the NameIdentifier claim
// (The sub claim from the JWT will be translated to the NameIdentifier claim)
return $"Token is validated. User Id {user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value}";
}
catch (Exception exc)
{
return "Invalid token: " + exc.Message;
}
}
}
}
I am trying to write a .netcore API which gets a bearer token from third party Webapp. This .netcore API should access the Microsoft graph API and get the user group information back from Azure AD.
I was following the sample project https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore.
But unfortunately this uses AAD graph rather tha Microsoft graph API.
I tried to implement Graph API in the .netcore api project in the above sample.
Things I have tried
I have changed the AAD graph to Graph API in the AzureAdAuthenticationBuilderExtensions.cs(in the web app project)
options.Resource = "https://graph.microsoft.com";
Also I used the Microsoft.Graph nuget in the API project. And I am trying to create the GraphServiceClient using the code below
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var words = accessToken.Split(' ');
var token = words[1];
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}
And finally I am trying to access the user information using the code below,
public async Task<IEnumerable<Group>> GetGroupAsync(string accessToken)
{
var graphClient = GetClient(accessToken);
try
{
User me = await graphClient.Me.Request().GetAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var user= await graphClient.Users["***"].Request().Expand("MemberOf").GetAsync();
var userEmail = "testemail#test.com";
var usergroup = await graphClient.Users[userEmail].GetMemberGroups(false).Request().PostAsync();
var groupList = new List<Group>();
foreach (var g in usergroup.CurrentPage)
{
var groupObject = await graphClient.Groups[g].Request().GetAsync();
groupList.Add(groupObject);
}
return groupList;
}
But when I try the code I am getting the error "Microsoft.Graph.ServiceException: Code: InvalidAuthenticationToken
Message: Access token validation failure.Inner error at Microsoft.Graph.HttpProvider."
Can somebody help me please?
Thanks in advance
The access token passed to GetGroupAsync is not correct , and i am confused why you need to split the token :
var words = accessToken.Split(' ');
var token = words[1];
But never mind , since you have modified options.Resource = "https://graph.microsoft.com"; ADAL will help you get access token for Microsoft Graph API in OnAuthorizationCodeReceived function , and save the tokens to cache .
To get the access token , you could use ADAL to get the token from cache :
AuthenticationResult result = null;
// Because we signed-in already in the WebApp, the userObjectId is know
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
// Using ADAL.Net, get a bearer token to access the TodoListService
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
result = await authContext.AcquireTokenSilentAsync("https://graph.microsoft.com", credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Then you could pass that token to your function:
await GetGroupAsync(result.AccessToken);
Modify your GetClient function to delete the split part:
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}