I have a JWT issued by app.vstoken.visualstudio.com.
When I tried to do validate that token using
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.Validate();
its throwing following exception
An unhandled exception of type 'Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException' occurred in System.IdentityModel.Tokens.Jwt.dll
IDX10500: Signature validation failed. No security keys were provided to validate the signature.
I suspect its because its not able to locate the public key for the issuer having
thumbprint = x5t attribute value present in the JWT header
How to get that public certificate or how to tell JwtSecurityTokenHandler.Validate() to fetch the required certificate to validate the JWT signature.
My first approach was to validate token issued by remote website locally.
To validate locally I needed to validate signature of the token using public key of the remote website (since it has used its private key to sign the token).
But instead of doing that I found out that the remote website exposes set of rest apis accessing which requires user to provide valid token as part of "bearer" header in the http request.
Currently I am using that api to determine authenticity of the user.
The remote website was dev.azure.com and it exposes set of apis and sdk to authenticate the token.
Library used : Microsoft.VisualStudio.Services.WebApi
Related
I successfully receive an access token from the following call on keycloak:
http://localhost/auth/realms/myrealm/protocol/openid-connect/token
using
clent_id=myclient
grant_type=password
username=someone
password=mypasswd
client_secret=1a5debfc-63c8-48e8-95cb-b42aa0187310
I can use the token I get from this call on jwt.io, and it verifies correctly with the client secret. However, the following code always gives me an invalid signature error using the same info:
const token = "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhOWY4NDhhYi05ZTllLTQ0ZDAtYWQ5NC1jN2VhMTBhMDMzOTIifQ.eyJleHAiOjE2Mzg1MDUwOTcsImlhdCI6MTYzODUwNDc5NywianRpIjoiMzYyZmJjNTgtMjM1Mi00YmM4LWE0NmUtMDliMGYzOTgzYzBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MC9hdXRoL3JlYWxtcy9teXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjhlMTM1NTZlLTc2M2MtNGQxOC05OGFlLWY0ZWQ1YWFjODFiZSIsInR5cCI6IkJlYXJlciIsImF6cCI6Im15Y2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImYzMWUxM2NiLWUyZDgtNGIxZC04MTQzLTgxMmU3YmE0NTQ5NiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1teXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImYzMWUxM2NiLWUyZDgtNGIxZC04MTQzLTgxMmU3YmE0NTQ5NiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWlrZSIsInByZWZlcnJlZF91c2VybmFtZSI6InNvbWVvbmUiLCJnaXZlbl9uYW1lIjoiTWlrZSIsImVtYWlsIjoic29tZW9uZUBzb21lZmFrZWRvbWFpbi5jb20ifQ.Ir2qhdGqJzbJPn8S9TzDP2RRmN207pc8y3UrD7cCD5Q";
const secret = "1a5debfc-63c8-48e8-95cb-b42aa0187310";
jsonwebtoken.verify(token, secret, { "algorithms": ["HS256"] });
What am I missing?
I've just been through this myself recently using the jsonwebtoken npm package in a NestJS application. The following solved the problem:
Check the answer here -> https://stackoverflow.com/a/64484150 - this explains where to get the actual client secret, because it is not shown in the Keycloak interface anywhere, the secret can only be retrieved from the Keycloak database via that SQL command
Once you have that you need to change your verify call to be this:
jsonwebtoken.verify(token, Buffer.from(secret, 'base64'), { "algorithms": ["HS256"] });
This is needed because the client secret from the database is base64 encoded.
I used this approach to reduce calls to the Keycloak server's /auth/realms/{{realm-id}}/protocol/openid-connect/userinfo endpoint which adds network overhead, but as mentioned in your comment Mike, that REST API is a valid approach.
due to the lack of INTROSPECT_ENDPOINT in azure AD, I am unable to validate the token.
How to validate the Azure Access token in Java?
Usually, the ADAL or the MSAL SDK will take care of it. But, you can still manually validate the access token you get. Here is the official tutorial: Validating tokens.
In summary, there would be 3 steps:
Get the kid in token header, and the tid in token payload.
Get all sign keys from https://login.microsoftonline.com/{tid_here}/discovery/v2.0/keys, and find the key with kid
x5c in the key is the public certificate. You can use it to verify the signature of a token.
There is an Endpoint to a backend server which gives a JSON response on pinging and is protected by an Apigee Edge Proxy. Currently, this endpoint has no security and we want to implement Bearer only token authentication for all the clients making the request.
All the clients making the requests to API will send that JWT token in Authorization Bearer and Apigee Edge will be used to verify the JWT Token.
How do I use Keycloak to generate this JWT token?
Also, Apigee needs a public key of the origin of the JWT token (the server which signed the JWT token, in this case, I believe that is Keycloak).
So my second doubt is, while I use Keycloak to generate the JWT token, how to get the public key using which the server will verify if the token is valid?
This got figured out with the help of this medium article. All the steps I have mentioned below have a detailed description in the article (Refer step 1 to 9 for token part, other steps are related to Spring Boot application) but I would like to give a overview of those in reference to my question.
Generating a JWT token using KeyCloak
Install and run KeyCloak server and go to the endpoint (e.g http://localhost:8080/auth). Log in with an initial admin login and password (username=admin, password=admin).
Create a Realm and a Client with openid-connect as the Client Protocol.
Create users, roles and map Client Role To User.
Assuming the server being on localhost, visiting the http://localhost:8080/auth/realms/dev/.well-known/openid-configuration gives details about all security endpoints
http://localhost:8080/auth/realms/dev/protocol/openid-connect/token sending a POST request with valid details to this URL gives the JWTtoken with.
Getting the public key of the KeyCloak server
Going to Realm Settings and click on Public key pops up with the Public key of the server for that Realm. Refer to this image for better understanding.
Add -----BEGIN PUBLIC KEY----- and append -----END PUBLIC KEY----- to this copied public key to use it anywhere to verify the JWTtoken. You public key should finally look something like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAj9OCZd0XjzOIad2VbUPSMoVK1X8hdD2Ad+jUXCzhZJf0RaN6B+79AW5jSgceAgyAtLXiBayLlaqSjZM6oyti9gc2M2BXzoDKLye+Tgpftd72Zreb4HpwKGpVrJ3H3Ip5DNLSD4a1ovAJ6Sahjb8z34T8c1OCnf5j70Y7i9t3y/j076XIUU4vWpAhI9LRAOkSLqDUE5L/ZdPmwTgK91Dy1fxUQ4d02Ly4MTwV2+4OaEHhIfDSvakLBeg4jLGOSxLY0y38DocYzMXe0exJXkLxqHKMznpgGrbps0TPfSK0c3q2PxQLczCD3n63HxbN8U9FPyGeMrz59PPpkwIDAQAB
-----END PUBLIC KEY-----
Validating the token on a third party platform
jwt.io is a great website for validating JWTtokens. All
we have to do is paste the token and public key. Read the introduction of the website here to know more about validating the tokens.
I have a perfectly working Spring Security web application that uses SAML SSO. The client (IdP) changed their certs. I updated the cert and the CA certs to my keystore.jks. I am getting redirected properly to the IdP, I log in and get properly redirected back to my app. At that point I am getting theses in the logs:
Attempting to validate signature using key from supplied credential (validate) (SignatureValidator.java:54)
Creating XMLSignature object (buildSignature) (SignatureValidator.java:90)
Validating signature with signature algorithm URI: http://www.w3.org/2000/09/xmldsig#rsa-sha1 (validate) (SignatureValidator.java:64)
Validation credential key algorithm 'RSA', key instance class 'sun.security.rsa.RSAPublicKeyImpl' (validate) (SignatureValidator.java:65)
Signature validated with key from supplied credential (validate) (SignatureValidator.java:70)
SSL negotiation with xxxxxx using candidate credential was successful (verifySignature) (BaseSignatureTrustEngine.java:148)
Successfully verifiServer certificate verify failed: signer not foundidate) (BaseSignatureTrustEngine.java:101)
Attempting to establish trust of KeyInfo-derived credential (validateConnected to HTTPS on 34.196.133.252)
Failed to validate untrusted credential against trusted key (validate) (ExplicitKeyTrustEvaluator.java:95
org.opensaml.xml.validation.ValidationException: Signature did not validate against the credential's key
So it looks like something is being validated, but I do not understand why it's failing. I double checked with keytool and all the CA's are there.
I have an Identity Server running based on IdentityServer 4 (.Net Core v2) targeting the full .Net framework, and I have an ASP.NET WebAPI built against ASP.Net Web API 2 (i.e. NOT .Net Core) that is using the Identity Server 3 OWIN middleware for token authentication.
When running locally, everything works just fine - I can use Postman to request an Access Token from the Identity Server using a RO Password flow, and I can then make a request to the WebAPI sending the token as a Bearer token - all works fine.
Now, when everything is hosted on our test servers, I get a problem when calling the WebAPI - I simply get an Unauthorized response. The token returned from the Identity server is ok (checked using http://jwt.io), but validation of the JWT is failing in the WebAPI.
On further investigation, after adding Katana logging, I see that a SecurityTokenInvalidAudienceException is being reported.
Audience validation failed. Audiences:
'https://11.22.33.44:1234/resources, XXXWebApi'. Did not match:
validationParameters.ValidAudience: 'https://localhost:1234/resources'
or validationParameters.ValidAudiences: 'null'
Looking at the JWT audience, we have:
aud: "https://11.22.33.44:1234/resources", "XXXWebApi"
In the WebAPI Startup, I have the call to
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = , // path to our local ID Server
ClientId = "XXXWebApi",
ClientSecret = "XXX_xxx-xxx-xxx-xxx",
RequiredScopes = new[] { "XXXWebApi" }
});
So the JWT audience looks ok, but its obviously not matching with what is supplied by the middleware (built from the IdP discovery end point). I would have thought that because I am specifying the RequiredScopes to include XXXWebApi that would have been enough to match the JWTs audience but that seems to be ignored.
I'm unsure what to change in the WebAPI authentication options to make this work.
EDIT: I changed the WebAPI Token auth options to use the validation endpoint, and this also fails in the IdentityServer with the same error.
If I call the Identity Server introspection endpoint directly from Postman with the same token though, it succeeds.
Ok, so after a lot of head scratching and trying various things out I at least have something working.
I had to ensure the Identity Server was hosted against a publicly available DNS, and configure the Authority value in the IdentityServerBearerTokenAuthenticationOptions to use the same value.
That way, any tokens issued have the xx.yy.zz full domain name in the JWT audience (aud), and when the OWIN validation middleware in the WebAPI verifies the JWT it uses the same address for comparison rather than localhost.
I'm still slightly confused why the middleware cant just use the scope value for validation because the token was issued with the API resource scope (XXXWebAPi) in the audience, and the API is requesting the same scope id/name in the options as shown.
As far as I understand your WebAPI project is used as an API resource.
If so - remove the 'clientId' and 'clientSecret' from the UseIdentityServerBearerTokenAuthentication, keep the 'RequiredScopes' and the authority (you may also need to set ValidationMode = ValidationMode.Both).
You need them, when you are using reference tokens. From what you've said - you are using a JWT one. Check here, here and here.