How to validate JWT with only a public key

Hello,

we have an APIM policy attempting to validate incoming JWT using public key modulus, exponent and id.

This policy however seems to not validate JWT signature for some reason. When I change key modulus, exponent or id, the validation still passes and request is forwarder to backend. If I change audience or issuer in the policy, the validation fails as expected.

What is the proper way to validate incoming JWT when we only have acess to public key of the identity provider?

  1. Deleted

    This comment has been deleted due to a violation of our Code of Conduct. The comment was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.

  2. Neščivera Ján (ERNI) 65 Reputation points

    unfortunatelly I can not add the coresponding policy to the question, as it immedietely gets deleted for violating code of conduct (altought its just a simple XML)

  3. Deleted

    This comment has been deleted due to a violation of our Code of Conduct. The comment was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.

  4. Deleted

    This comment has been deleted due to a violation of our Code of Conduct. The comment was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.

  5. Neščivera Ján (ERNI) 65 Reputation points

    the screenshot of corresponding policy fragment (as when adding as XML its flagged as violating Code of conduct)
    👁 User's image


Sign in to comment

Answer accepted by question author

Siddhesh Desai 7,480 Reputation points Microsoft External Staff Moderator

Hey

Thank you for reaching out to Microsoft Q&A.

As discussed on teams and with the Product team backend.

The validation is failing as expected now when n/e changes to some wrong value however when key id in policy is changed to some wrong value (aka. value not matching kid claim in JWT), the JWT validation passes which is an expected behavior. The Product team will update the Documentation accordingly.

If the resolution was helpful, kindly take a moment to click on 👁 210246-screenshot-2021-12-10-121802.png
and click on Yes
for was this answer helpful. And, if you have any further query do let us know.

  1. Neščivera Ján (ERNI) 65 Reputation points

    Hello,

    as written in my original question and as visible in provided policy configuration, there is no <openid-config> in our policy, so the suggestions you provided are not relewant

  2. Pravallika KV 17,025 Reputation points Microsoft External Staff Moderator

    @Neščivera Ján (ERNI) ,Could you please confirm the following:

    1. What is the alg value in the JWT header, for example RS256, HS256?
    2. Is the policy fragment containing <validate-jwt> definitely applied in the inbound processing pipeline of the API being tested?
  3. Neščivera Ján (ERNI) 65 Reputation points

    Hello,

    the alg value in our JWTs is RS256. The policy fragment is applied to in the inbound processing pipeline of our API operations that need the JWT validated (but seems to work for audience/issuer only, unfortunatelly).

    Is there a way to debug the pipeline processing and see why the policy fragment cosidered the JWT valid even with mismatched key id? It really seems that signature validation is for some reason skipped/ignored, only audience and issuer work.

  4. Pravallika KV 17,025 Reputation points Microsoft External Staff Moderator

    You can try debugging this using APIM Trace.

    Enable Trace in the Azure Portal: Go to your API in APIM=> Open Test tab => Send a request with a valid JWT=>Enable Trace and inspect the validate-jwt step.

    You should see whether APIM is selecting the signing key (kid), performing signature verification or only validating issuer/audience/claims.

    If there is no key resolution or signature validation step in the trace, then the <issuer-signing-keys> block is not being applied during execution. Also verify the effective policy at runtime (API / operation / product level), since another policy layer can override or bypass signature validation.

  5. Neščivera Ján (ERNI) 65 Reputation points

    hello,

    I checked it with tracing and I can see that the policy fragmet is included as expected. If I change the audence in policy fragment I can see in trace that it evaluated the audience and failed. With correct audience I can see in trace the message "JWT validation succeeded". Nothing regarding evaluating the signature.

    Even if i change the kid to some wrong value, the only message in trace is "JWT validation succeeded", so the <issuer-sogning-key> is ignored for some reason.


Sign in to comment

1 additional answer

  1. AI answer

    Use the validate-jwt policy with an asymmetric issuer signing key configured from the public key. The signature is validated against the public key; if the modulus/exponent are wrong, validation must fail.

    To validate with only a public key, configure one of these options in the validate-jwt policy:

    1. Use an OpenID configuration endpoint (recommended)
    <validate-jwt header-name="Authorization" require-scheme="Bearer">
     <openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
     <audiences>
     <audience>api://your-api-app-id</audience>
     </audiences>
     <issuers>
     <issuer>https://login.microsoftonline.com/{tenant}/v2.0</issuer>
     </issuers>
    </validate-jwt>
    

    openid-config causes API Management to pull the JWKS (public keys) from the identity provider and use them to validate the JWT signature. This is the simplest and most robust way when only the public key is available.

    1. Use modulus and exponent directly

    If the OpenID configuration endpoint cannot be used, specify the RSA public key via issuer-signing-keys with n and e:

    <validate-jwt header-name="Authorization" require-scheme="Bearer">
     <issuer-signing-keys>
     <key id="your-kid" n="{base64url-modulus}" e="{base64url-exponent}" />
     </issuer-signing-keys>
     <audiences>
     <audience>api://your-api-app-id</audience>
     </audiences>
     <issuers>
     <issuer>https://your-issuer</issuer>
     </issuers>
    </validate-jwt>
    

    Key points to ensure signature is actually validated:

    1. Do not disable signing

    Make sure require-signed-tokens is not set to false:

    <validate-jwt header-name="Authorization" require-scheme="Bearer" require-signed-tokens="true">
     ...
    </validate-jwt>
    

    If require-signed-tokens is false, the token is accepted without signature validation, which matches the behavior described (changing key values has no effect).

    1. Configure issuer-signing-keys correctly

    Within issuer-signing-keys:

    • Use <key id="..." n="..." e="..." /> for an RSA public key.
    • n and e must be Base64url-encoded values of the modulus and exponent.
    • If id is set, it is matched to the token’s kid claim; if no kid matches, API Management will try all keys.

    If the key element is misconfigured but require-signed-tokens is false, the token still passes. With require-signed-tokens="true", a mismatched n/e causes validation to fail.

    1. Ensure other required elements are present

    For full validation, configure:

    <audiences>
     <audience>...</audience>
    </audiences>
    <issuers>
     <issuer>...</issuer>
    </issuers>
    

    and keep require-expiration-time at its default true unless there is a specific reason to allow tokens without exp.

    1. Alternative: certificate-based public key

    If the public key is available as a certificate uploaded to API Management, use certificate-id instead of n/e:

    <issuer-signing-keys>
     <key certificate-id="your-cert-id" />
    </issuer-signing-keys>
    

    This also validates the signature using the public key from the certificate.

    In summary, to ensure the JWT signature is actually validated when only a public key is available, configure validate-jwt with either openid-config or issuer-signing-keys (n/e or certificate-id) and keep require-signed-tokens="true". If changing modulus/exponent has no effect, the policy is likely not using those keys (for example, because require-signed-tokens is disabled or the token is not signed with an asymmetric algorithm supported by the policy).


    References:

    AI-generated content may be incorrect. Read our transparency notes for more information.
Sign in to answer

Your answer