A Joken.Hooks
implementation that builds a signer out of a JWKS url for verification.
Please see our documentation for usage.
def deps do
[
{:joken_jwks, "~> 1.6.0"}
]
end
When using JWTs for digital signing we need a key to sign and verify the contents of the token. Many times the part that is doing signature verification is the same that does the signing and so, it possesses both the private and public key. Other times, this is not the case.
When using an authentication server that is different than the "business" server, the later has no control about the keys being used to authenticate a request. On these cases, the authentication server publishes its own public key so that anyone can validate tokens that it has generated before with the matching private key.
This scenario is common on delegated authentication as mentioned. One specification that uses this approach is OAuth2 with OpenID Connect. The auth server has a public configuration endpoint where there is plenty information about how to connect with the server. One such information is the JWKS (JSON Web Key Set): a list of public keys the server uses to generate signed tokens.
Example of well known JWKS URIs:
- Google: https://www.googleapis.com/oauth2/v3/certs
- Microsoft: https://login.microsoftonline.com/common/discovery/keys
If your server is using Joken 2 to validate tokens, then you can use this hook to retrieve the list of signers from a JWKS URI. This is how it happens:
- Build you token configuration (either with
use Joken.Config
or directly) - Initialize your fetching strategy
- Pass this hook to your verify call:
- If you are using
Joken.Config
then it's just a matter ofadd_hook(JokenJwks, strategy: <<YOUR STRATEGY>>)
- If you are calling
Joken.verify_*
directly, you can pass the hook as a last parameter
- If you are using
Remember that there can't be a default signer otherwise it will have precedence over this!
This hook will call the behaviour JokenJwks.SignerMatchstrategy
for every token. It is the implementation job to decide how to choose a signer for the token.
The only occasion where this will not call the behaviour is if the given token is not properly formed nor it contains a "kid" claim on its header. JWKS mandates this claim so that we know which key to use for verifying.
A very naive approach of implementing the callback would be to fetch signers upon startup and then re-fetching every time a token kid does not match with the loaded cache.
This could potentially open an attack vector for massively hitting the authentication server. Of course, the auth server JWKS url is public and an attacker could just hit it directly, but it is wise to have some defense mechanism in place when developing your strategy.
JokenJwks
comes with a smart enough implementation that uses a time window approach for re-fetching signers. By default, it polls the cache state every minute to see if a bad kid was attempted. If so, it re-fetches the cache. So, it will fetch JWKS once every minute tops.
Since the JWKS specification is just that, a specification, many servers might disagree on how to implement this. For example, Google specifies the "alg" claim on every key instance. Microsoft does not. Therefore we assume some interpretations:
- Every key must have a "kid" (even if there is only one key)
- We skip keys that are used for encryption (which have a field "use" with value "enc")
- We skip keys that have JWE algs or unsupported JWS algorithms
- If no "alg" claim is provided, then the user must pass the option "explicit_alg"
That's it for now :)
Copyright (c) 2018 Victor Nascimento
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.