I'm trying to implement caching of public key provided by openid-configuration/jwks endpoint of our JWT provider. I want to use cached value for validation of signature of incoming request. We want to have cache in place in order to lower requesting jwks endpoint. So we decided to create custom APIM policy in order to do that. After few hours of struggles I found myself clueless. Therefore I would like to address some questions about APIM xml policies.
How can I read request JWTs kid or x5t in policy? Regarding to APIM policy expressions calling AsJwt() returns Jwt object which does not contains Header property which should contain required fields
I tryed Id, Claims with no successful reach of kid or x5t (<set-variable name="requestKid" value="#(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.____)"/>)
When retrieving JWKs from the provider endpoint I'm not able to store various fields of response into separate context variables, it always fails on second <set-variable. Seems like (IResponse)context.Variables["jwksResponse"]) is null when calling second time. Does context variables get destroyed after first read?
<send-request mode="new" response-variable-name="jwksResponse" timeout="10" ignore-error="true">
<set-url>https://some.identity.server/.well-known/openid-configuration/jwks</set-url>
<set-method>GET</set-method>
</send-request>
<set-variable name="jwksn" value="#(((IResponse)context.Variables["jwksResponse"]).Body.As<JObject>()["keys"][0]["n"])" />
<set-variable name="jwkse" value="#(((IResponse)context.Variables["jwksResponse"]).Body.As<JObject>()["keys"][0]["e"])" />
How can I set key exponent and modulus attributes? When I use variables from previous step I got xml validation Error: "ValidationError" - "Error in element 'validate-jwt' on line 8, column 4: value is not a valid base64url string." while It works when when hardcoded. (I also tried hacks with Convert.ToBase64String(), with no success, to me seems like xml validator issue - can it be overriden?)
<issuer-signing-keys>
<key e="#((string)context.Variables["jwkse"])" n="#((string)context.Variables["jwksn"])"/>
</issuer-signing-keys>
Can I use x5c value retrieved from our JWKs provider as key value? like so:
<issuer-signing-keys>
<key>#((string)context.Variables["jwksx5c"])</key>
</issuer-signing-keys>
After further investigation we found gitHub thread regarding caching of JWKs when using JWT validation with OpenId. It seems JKWs are cached for 1 hour and also recached when validation fails. However this behavior is not stated in documentation.
Related
I am using docusign for digital signature , where I have to create a jwt token. For this I have been using code from git repo https://github.com/docusign/docusign-python-client.
docusign version 3.1.0 , python version 3.5 and 3.6 in sandbox mode .
getting following error
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='https', port=443): Max retries exceeded with url http://account-d.docusign.com/oauth/token (Caused by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known',))
i have followed the curl request using following url JSON Web Token (JWT) Grant
got an application token and while passing in auth giving me 401(401 UNAUTHORIZED
) error
postman
claim = {"iss": '4556e2f7-4a3d-41f9-a0c3-18535a28ab2a', "aud": 'account-d.docusign.com', "iat": now, "exp": later, "scope": " ".join(scopes)} token = jwt.encode(payload=claim, key=pkey, algorithm='RS256').decode("utf-8")
i getting application token using above code but when i pass all the required parameter i got {"error":"invalid_grant","error_description":"no_valid_keys_or_signatures"} but when i pasted my jwt token in jwt.io with my public and privte key its says signature verified.
I have added the links of screenshots, that might also help to understand more about the problem.
https://ibb.co/2yKXNCW https://ibb.co/cFTk6R1 https://ibb.co/t3YMkr2
The concept is to generate a key with 3 elements (Header RS256 Algorith, the Payload Data that contains the application date and token validity and the private and public key. On the site jwt.io we will put all this information and the site will retour an Base64 Key we can further entered on
Open the JWT.IO site jwt.io
Choose Algorithm RS256 on the top
For the second Pane : “Payload: Data”
https://admindemo.docusign.com/apps-and-keys
iss :
The integration key (also known as client ID) of the application.
sub : The User ID of the user to be impersonated.
(The person granting permission must have a DocuSign user account.)
The provided User ID must be in GUID (not email) format. You can look up a user’s GUID from their email using the users.list method.
Note: If you don’t already have the User ID of the user to act on behalf of, you can obtain it by completing the Authorization Code Grant flow begun in the Request the authorization code step.
https://admindemo.docusign.com/apps-and-keys -> Apps and Keys ->
aud : The URI of the authentication service instance to be used.
For demo environments, use account-d.docusign.com.
For production environments, use account.docusign.com.
Important: Do not include https:// in the aud value!
iat : The date-time when the JWT was issued
in Unix epoch format. You must include an iat value when creating a JWT, typically set to the current time.
You can convert a date to linux epoch format with the following link : https://www.epochconverter.com/
exp: The date-time when the JWT assertion will expire
in Unix epoch format. Defaults to one hour from the value of iat and cannot be set to a greater value. You must include an exp value when creating a JWT.
Use the epoch https://www.epochconverter.com/
⚠ Please note that this value must not be too far in time, in our example we are using 14 years of validity.
scope : The scopes to request. In our example “signature impersonation”.
All the scopes are defined on the following site https://developers.docusign.com/platform/auth/reference/scopes/
{
"iss":"ea670856-0422-4d8e-99bd-80560d323639",
"sub":"917c9fc5-d615-43bb-8a28-87e7ede3b8ac",
"aud":"account-d.docusign.com",
"iat": 1628496664,
"exp":1912493464,
"scope":"signature impersonation"
}
For the Pane “Verify Signature”
https://admindemo.docusign.com/apps-and-keys -> Actions -> Edit
… -> Click on “Generate RSA”
Select the Public and Paste it on the section
And make the same for the public key. If everything is OK, you must have the Tag “Signature Verified” at the left of the document, see the picture bellow.
For the final step, you obtain the key for stating using the API
Go to Postman
https://www.postman.com/use-cases/api-testing-automation/
Sign in (for me with my google account)
You must fille the following values
Select “Post”
Put the API Value : https://account-d.docusign.com/oauth/token
Click on the “Body” section
Add the key “grant_type”
Add the value “urn:ietf:params:oauth:grant-type:jwt-bearer” for the key “grant_type”
Add the key “assertion”
Add the generate value from the site jwt on the previous steps
Finally Click on Send for obtain the “access_token” necessary the the further call.
[enter image description here][1]
Here are the things that you must ensure:
You have an Integration key (clientID) that is configured correctly.
You have an RSA Private key. that key was copied/pasted exactly as given to your configuration file. New lines must be preserved
Your URLs are matching the environment. Meaning you use account-d and demo.docusign.net for the sandbox env endpoints.
You need the userId which is a GUid for the user that would be impersonated. That user must consent to the application. You have to ensure you pass userId and not accountId and that it is for the same account that you would be using.
If you confirm all of this and still get an error - I would consider to use our code example to start. I don't see code in your question, but our code example should be a good way to start.
We generated JWT using docusign given private key and validated by Docusign public key in jwt.io site. It generated valid signature.
Using same signature we called Docusign demo server for access token
POST https://account-d.docusign.com/oauth/token
with
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
assertion=Signature generated
but getting error "Invalid Grant".
What could be the possible reason? If signature is already verified in jwt.io with public key, Docusign should accept the assertion value.
As documented, you also need to supply the following claims:
iss--The integration key (also known as client ID) of the application.
sub--The user ID of the user to be impersonated.
iat--The DateTime when the JWT was issued, in Unix epoch format.
exp--The DateTime when the JWT assertion will expire, in Unix epoch format. Use 1 hour after iat or less.
aud--domain name of the authentication service instance to be used. For demo environments, use account-d.docusign.com For production environments, use account.docusign.com. Note: Do not include https:// in the aud value!
scope--The scopes being requested. For the JWT bearer grant, the requested scope should be signature.
See the docs and also see the DocuSign JWT code examples, the repos named eg-01-*
Ask a new question if you'd like further help.
I try to limit access to a REST API using a JWT token using the validate-jwt policy. Never did that before.
Here's my inbound policy (taken from the point Simple token validation here):
<validate-jwt header-name="Authorization" require-scheme="Bearer">
<issuer-signing-keys>
<key>{{jwt-signing-key}}</key>
</issuer-signing-keys>
<audiences>
<audience>CustomerNameNotDns</audience>
</audiences>
<issuers>
<issuer>MyCompanyNameNotDns</issuer>
</issuers>
</validate-jwt>
Using this generator I created a claim (I'm not sure whether I understood issuer and audience correctly):
{
"iss": "MyCompanyNameNotDns",
"iat": 1572360380,
"exp": 2361278784,
"aud": "CustomerNameNotDns",
"sub": "Auth"
}
In the section Signed JSON Web Token I picked Generate 64-bit key from the combo box. The key that was generated I put in the place of {{jwt-signing-key}}.
Now, I'm trying to call the API using Postman. I add an "Authorization" header, and as the value I put "Bearer {{ JWT created by the linked generator }}".
I get 401, JWT not present. What am I doing wrong?
According to my research, If you use HS256 signing algorithms, the key must be provided inline within the policy in the base64 encoded form. In other words, we must encode the key as base64 string. For more details, please refer to the document
My test steps are as below
Create Jwt token
Test
a. If I directly provide the key in the policy, I get the 401 error
b. If I encode the key as base64 string in the policy, I can call the api
Jim Xu's answer to encode the key as base64 string in the policy allowed me to get this far :-)
I set the Ocp-Apim-Trace parameter to true in order to debug it more closely. I followed the link provided in the response under ocp-apim-trace-location, and in the "on error" section I found the following message:
JWT Validation Failed: IDX10225: Lifetime validation failed. The token is missing an Expiration Time. Tokentype: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'..
Which is funny, because I have set the Expiration Time... to 2099.
I changed it to a month from now and it worked just fine.
I am trying to authenticate using the Azure Storage emulator's fixed account/key used for the Azure storage emulator Shared Key authentication.
When sending an anonymous request I get the correct response
but when adding Authorization Header I get:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<code>InvalidAuthenticationInfo</code>
<message xml:lang="en-US">Authentication information is not given in the correct format. Check the value of Authorization header.
RequestId:6d2cc79e-6bce-451c-a6f0-f10e0876f640
Time:2019-07-29T19:22:48.6402756Z</message>
</error>.
This is the key-value pair for the Authorization header:
Any idea on how to resolve this? I have followed documentation but no luck.
Considering you're using a Shared Access Signature (SAS) URL, you don't need to add Authorization header as authorization information is already included in your SAS URL (sig part of your URL).
One thing that you may want to do is change the value of Accept header and set its value to application/json;odata=fullmetadata.
Authorization header comes into picture when you don't use SAS. I noted that you're simply passing your account key as part of your authorization header. That won't work. You will actually need to compute the authorization header. Please see this link for more details: https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key.
I setup caching for discovery endpoint below by wrapping it and caching it via Azure API Management.
https://openid-connect-eu.onelogin.com/oidc/.well-known/openid-configuration
So the new link below does the caching:
https://my.azure-api.net/sso/.well-known/openid-configuration?subscription-key=mykey
Below is policy for token validation:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Error: expired token or invalid token" require-expiration-time="true" require-scheme="Bearer" require-signed-tokens="true">
<openid-config url="https://my.azure-api.net/sso/.well-known/openid-configuration?subscription-key=mykey" />
<audiences>
<audience>id</audience>
</audiences>
<issuers>
<issuer>https://openid-connect-eu.onelogin.com/oidc</issuer>
</issuers>
</validate-jwt>
My question is that do I need to cache the JWKS link below that is on the discovery document above and used for the validation? If so, how can I cache it?
https://openid-connect-eu.onelogin.com/oidc/certs
You will need to cache the contents of the JWKS endpoint somewhere in the service that you are trying to validate the requesting JWT. A good way to cache these keys is to use a caching library that will cache the keys at the service level for a specified amount of time. The library that I use in my services is called caffeine by Ben Mames and can be found here. Here is a quick example of how you could cache a JWK for 30 minutes:
cache = Caffeine.newBuilder()
.maximumSize(5)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build(k -> jwksMap.get(k));
Your service could then refetch the keys from the endpoint every 30 minutes to refresh the cache.
I do not know the reason why you're caching this document, but both metadata endpoint (https://openid-connect-eu.onelogin.com/oidc/.well-known/openid-configuration) and
key set endpoint (https://openid-connect-eu.onelogin.com/oidc/certs) are fetched by APIM from within validate-jwt policy.
The url on the html body returned is modified and replaced with a new url that is cached via APIM.