-
Notifications
You must be signed in to change notification settings - Fork 373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add asymmetric jwt support #1674
Conversation
f828ede
to
e2751e1
Compare
e2751e1
to
ef47fe0
Compare
Pull Request Test Coverage Report for Build 10112912238Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For GOTURE_JWT_KEYS
:
- Why is the
in_use
field there? If it's configured, it's in use. - I'd prefer if the key is encoded as JWK instead of DER, as it aids readability. You also don't need to specify
type
for it at all in that case. Example:
GOTRUE_JWT_KEYS = [
{
// HMAC key
kid: '<ID>',
alg: 'HS256',
kty: 'oct',
k: '<Base64URL of the HMAC symmetric key>',
},
{
kty: 'EC',
x: 'JTeOUQoO2zicwixLzfIHawyvM-wQsMEI1EAtI4NVTdI',
y: 'YgxS33NnJfFIjLeLoWOsFCyAghGOWBzgWbPrENNiMyg',
crv: 'secp256k1',
d: 'jaGPBvQHFIaPlgRMICknHNeZxVzx2gyEFlM5T_Le6SM' // this is the "private key" for x and y which are the public key
},
// etc
]
Then you can easily parse that config string into an array of json.RawMessage
that you can directly pass to jwk.ParseKey
.
Finally for exposing this in the /.well-known/jwks.json
endpoint you can just iterate over those parsed JWK objects and call the Public()
method to convert the private keys into public ones and then just marshal those objects to JSON.
In fact, the whole in_use
complication can just be omitted completely and you can rely on whether the key in the GOTRUE_JWT_KEYS
config is private or public. If it's a private key, then it can be used for signing, otherwise it can only be used for verification.
d771cc4
to
a2e1cb1
Compare
fb88f32
to
6d6092e
Compare
38881c7
to
ad4d32c
Compare
d67cf27
to
2b6fd93
Compare
7654407
to
031796b
Compare
8b62893
to
a36c511
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice.
🤖 I have created a release *beep* *boop* --- ## [2.157.0](v2.156.0...v2.157.0) (2024-07-26) ### Features * add asymmetric jwt support ([#1674](#1674)) ([c7a2be3](c7a2be3)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## What kind of change does this PR introduce? * Adds asymmetric JWT support to auth, with zero downtime key rotation ## What is the current behavior? * Auth only supports symmetric JWTs which involves some downtime if the key needs to be rolled ## What is the new behavior? ### Config changes * Accepts a new env var `GOTRUE_JWT_KEYS` which takes in an array of JWK * The private key is encoded as a JWK that contains the kid, use and alg claims * Defaults to use `GOTRUE_JWT_SECRET` and `GOTRUE_JWT_KEY_ID` if `GOTRUE_JWT_KEYS` is missing, which is just the JWK representation of the symmetric secret * On config initialisation, `GOTRUE_JWT_KEYS` is transformed and stored as JWKs in-memory. * We use the `key_ops` claim in the JWK and to detect if they should be used to sign or verify a JWT (see [RFC](https://datatracker.ietf.org/doc/html/rfc7517#section-4.2)) * All JWKs represented as public keys will have the `key_ops` claim set to `["verify"]`, while the JWK represented as private keys will have the `key_ops` claim set to `["sign", "verify"]` if it is used for signing ### Endpoint * `GET /.well-known/jwks.json`: returns the JWKs for the auth service. Given the following config (generated from [this script](https://gist.github.com/kangmingtay/a1c83d9e1ea1f398d9388e2188deab2b)): ```bash GOTRUE_JWT_KEYS='[{"kty":"oct","k":"KtxwCvCPABNiOmUBij2_uzlO8FM477lO1zpe_E6nQhE","kid":"81763ee4-803e-4420-bed2-6849ef963262","key_ops":["verify"],"alg":"HS256"},{"kty":"RSA","n":"htA_Lzcc3qojwvcrF1JU6yPPRLvxvCp8x3tx_lCO6GyBFktE6HLsIHEpcWfvkiJfwxMZ4npn2CWI4rjjNbT2BHqax7CUOgGFATNZe13kTukx8SUQY3GHCIzPiN39oc55HcMBB_u4sLQBFD3RUCEcLqrlvwYRcTuCY317Xyn3j1YZogZ9gm6fY70v0Sj2hxLxtURr0UQurqhqRqUbXcujI6x3JqKKuk4-1o_K6J8j97hj4AcGMjRgmyi7G_7jM9hZG2SPJiFP7kbCpU1iT0rYYZptxVNUpWe6u5kg6onzXUE_s7Wu64YT7FE7xIFLg9MUrohBqWqrOjmF4IqTaU95Nw","e":"AQAB","d":"CM6rChEeLDfOTUrrgEMLNC9rN5DVupbF_xxD9rrZkzqfdk7lihAT-AycigGhx5jCS9LAIqkfhqHxHuq4QUZ4uhMucHRLQrzdrRXnNyWLqFIYxqnGt9BvY3IbjtP94WfFRtn6A8UArF6eIW3mckcveacFimTBl_Wsz4YfnLh3qV_817F9j8XQCRaaDfNVOF3_EFb61Ewvb4OxM_fa600wL4gJtnwwfQo_57E8bsvGJXYKvDLS1_3T-vrhARjZb3v-mHm7oXbl-0s_7L1orMRBJM1V5Ay5zFYcVr6ko7im4s0AZ-PA5Hkc_qk4rwzG8mrMWXBHi-NarCm-TG3dy6hBhQ","p":"udGq0glcgYqO6XRElohOJgOuQgdKbOodA0rojf1UkiSOHTcTKRqQYVeiYQAQUHfW3eyyNZt3XuZp_--SfjQMctYjD-SmPp2rpxt5Dz7ffFnNB3aKxBgzbiIMT3XHbONLEgRl0ohWzIzHBjZy6rOIqTUaAg2245DQRaZYnua8YdM","q":"ubr-DMx9AamMUcAIv4-aKwMcS_k15NLqulLwm6hjfDh5yezQw_-LWBpg5QxkFAf04lhJa__nAycLzdwpRqJ-u6P6EazGpeqtJtb4n5Zq8kE74ksouDCmymk9Nj5r2aZsfqZdDt5L3IId4AkzTM0yXhEV77dFupidVQZQy18lCI0","dp":"fd_dMnjq9EnTM6vyRnLBVZkKs2nS7eLNkoxs6rqgTnt61amYTjDTe01tDv6HDquPnzgXJJ9TBrNZPOmiN-G0SRpsF_kQ8LvIKuQ-Zqh1pfwDGrofmGS4ejOQWUd0t3tlQChAfZSkD96Rd9DsmbbSraTuIFP__zn7DCN6RvIQzMc","dq":"QuSqY4my7EpYk4kKnZPm_t7b7jEPzB57FCiTKDz5t9_PXX7BohYD5fN6OoS_9sb22B7cMt20IlqJ0dcdtqcH5iUlCACme1OOkZKTcUcHtcDxBIv1WoGLUROeTE8nIPjj0qmwko5V3FGw2OP3ag3tuhuFPxVPM-mLoPfpWZYnDHE","qi":"cyCVFBj5wM1-5syqt0xVuW5U8w6ZaPMM2F4GlkCu6lb-vNkymgmK3uGvr6p5VpJGU4UsEk1yrH3KrzlBHE4j8ssSMx4CLmq6Hpf4c9zkR8fO8-lgBdhyPmlHyIDvloeJqs4503qhFZO30UMTjVEKL6Wk_83CF00JkOhMo1uyv-A","kid":"38285a37-2843-48f9-a69c-6d72f8c4f016","key_ops":["verify"],"alg":"RS256"},{"kty":"EC","x":"8Whe4H2LCoTN4SODA7GIUrFYD-CpoYS7EvUsbOfcjn8","y":"Vs7VB8ozhyUkSy951Sq4clynrgg8URX91f6FjNDy18k","crv":"P-256","d":"UWPQ4T7opsJhdPbTohO0hf0noTkqGlEWOQrP0l_Tteo","kid":"406f824a-a71b-435d-8e0e-12ee8b07f88b","key_ops":["sign","verify"],"alg":"ES256"},{"crv":"Ed25519","d":"6MooMObDBKW1QRe1uHnrzKoWY9iJKcY55kD1rSY9jiI","x":"4yF8m6gflwZntZMc12j4hIUZFuZ5XJlAqbpnlEIgSxk","kty":"OKP","kid":"a678b12f-0f67-4802-ba7a-a3ad0ea5cc17","key_ops":["verify"],"alg":"EdDSA"}]' ``` The response returned is: ```json { "keys": [ { "alg": "ES256", "crv": "P-256", "kid": "fa6f71ab-03e2-435f-b2f3-9b6143a9c295", "kty": "EC", "use": "sig", "key_ops": ["verify"], "x": "3HcQyhPGXE9Tr_7VMIUvh-PJfQ_nXe_d2Ho7HWefJLA", "y": "CAdc8gjfA8eIwDjWzEdeRurZFUHs_OZ-SEMWcW_UUaE" }, { "alg": "RS256", "e": "AQAB", "kid": "479be47e-ca6e-44cd-a638-b17754611553", "kty": "RSA", "n": "16Tia3phliCATvt0VtISvrWazjIw_BN6a7b5p9VerjoaZfx98l6DJfRrxi2RW6aijPPuzT3DYTfHqNkb1SQUGPKQa_OXGDzPPFXT9RTma24I6vi2VjuLmDPQI6vpAiLqCuBHQ_5gMlyN8wa7F1r86Mf1F-14p_78tBBpea1zSPFcgaODjfOens8Om2CV_eKlK-_zPMCacM96X2Gtx00QVS1UEClQaiZWMvwfdprWfg9w8D2C4ze5wNWIVeMINeO-Ajug3nvhw8UJwZS-ZqiIVD34e3gCukc4bAht-F6xO7RGmOry7UpTe-56r9reaRfpN-W2G2si_sb5zikaJuQcEQ", "use": "sig", "key_ops": ["verify"] } ] } ``` ### JWT changes * Now supports encrypted and verifying using EC, RSA, Ed25519 and HMAC * Change the `use` claim in the JWK from `sig` to `enc` to specify the signing/encryption key - this conforms with the JWK spec. * `GOTRUE_JWT_KEYS` will default to use `GOTRUE_JWT_SECRET` if it's not set * A key can continue to be used for verification as long as it's present in `GOTRUE_JWT_KEYS` * A revoked key is one that is removed from `GOTRUE_JWT_KEYS` * `ValidMethods` are computed on config initialisation so we don't have to do that on every request
🤖 I have created a release *beep* *boop* --- ## [2.157.0](supabase/auth@v2.156.0...v2.157.0) (2024-07-26) ### Features * add asymmetric jwt support ([supabase#1674](supabase#1674)) ([c7a2be3](supabase@c7a2be3)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## What kind of change does this PR introduce? * Adds asymmetric JWT support to auth, with zero downtime key rotation ## What is the current behavior? * Auth only supports symmetric JWTs which involves some downtime if the key needs to be rolled ## What is the new behavior? ### Config changes * Accepts a new env var `GOTRUE_JWT_KEYS` which takes in an array of JWK * The private key is encoded as a JWK that contains the kid, use and alg claims * Defaults to use `GOTRUE_JWT_SECRET` and `GOTRUE_JWT_KEY_ID` if `GOTRUE_JWT_KEYS` is missing, which is just the JWK representation of the symmetric secret * On config initialisation, `GOTRUE_JWT_KEYS` is transformed and stored as JWKs in-memory. * We use the `key_ops` claim in the JWK and to detect if they should be used to sign or verify a JWT (see [RFC](https://datatracker.ietf.org/doc/html/rfc7517#section-4.2)) * All JWKs represented as public keys will have the `key_ops` claim set to `["verify"]`, while the JWK represented as private keys will have the `key_ops` claim set to `["sign", "verify"]` if it is used for signing ### Endpoint * `GET /.well-known/jwks.json`: returns the JWKs for the auth service. Given the following config (generated from [this script](https://gist.github.com/kangmingtay/a1c83d9e1ea1f398d9388e2188deab2b)): ```bash GOTRUE_JWT_KEYS='[{"kty":"oct","k":"KtxwCvCPABNiOmUBij2_uzlO8FM477lO1zpe_E6nQhE","kid":"81763ee4-803e-4420-bed2-6849ef963262","key_ops":["verify"],"alg":"HS256"},{"kty":"RSA","n":"htA_Lzcc3qojwvcrF1JU6yPPRLvxvCp8x3tx_lCO6GyBFktE6HLsIHEpcWfvkiJfwxMZ4npn2CWI4rjjNbT2BHqax7CUOgGFATNZe13kTukx8SUQY3GHCIzPiN39oc55HcMBB_u4sLQBFD3RUCEcLqrlvwYRcTuCY317Xyn3j1YZogZ9gm6fY70v0Sj2hxLxtURr0UQurqhqRqUbXcujI6x3JqKKuk4-1o_K6J8j97hj4AcGMjRgmyi7G_7jM9hZG2SPJiFP7kbCpU1iT0rYYZptxVNUpWe6u5kg6onzXUE_s7Wu64YT7FE7xIFLg9MUrohBqWqrOjmF4IqTaU95Nw","e":"AQAB","d":"CM6rChEeLDfOTUrrgEMLNC9rN5DVupbF_xxD9rrZkzqfdk7lihAT-AycigGhx5jCS9LAIqkfhqHxHuq4QUZ4uhMucHRLQrzdrRXnNyWLqFIYxqnGt9BvY3IbjtP94WfFRtn6A8UArF6eIW3mckcveacFimTBl_Wsz4YfnLh3qV_817F9j8XQCRaaDfNVOF3_EFb61Ewvb4OxM_fa600wL4gJtnwwfQo_57E8bsvGJXYKvDLS1_3T-vrhARjZb3v-mHm7oXbl-0s_7L1orMRBJM1V5Ay5zFYcVr6ko7im4s0AZ-PA5Hkc_qk4rwzG8mrMWXBHi-NarCm-TG3dy6hBhQ","p":"udGq0glcgYqO6XRElohOJgOuQgdKbOodA0rojf1UkiSOHTcTKRqQYVeiYQAQUHfW3eyyNZt3XuZp_--SfjQMctYjD-SmPp2rpxt5Dz7ffFnNB3aKxBgzbiIMT3XHbONLEgRl0ohWzIzHBjZy6rOIqTUaAg2245DQRaZYnua8YdM","q":"ubr-DMx9AamMUcAIv4-aKwMcS_k15NLqulLwm6hjfDh5yezQw_-LWBpg5QxkFAf04lhJa__nAycLzdwpRqJ-u6P6EazGpeqtJtb4n5Zq8kE74ksouDCmymk9Nj5r2aZsfqZdDt5L3IId4AkzTM0yXhEV77dFupidVQZQy18lCI0","dp":"fd_dMnjq9EnTM6vyRnLBVZkKs2nS7eLNkoxs6rqgTnt61amYTjDTe01tDv6HDquPnzgXJJ9TBrNZPOmiN-G0SRpsF_kQ8LvIKuQ-Zqh1pfwDGrofmGS4ejOQWUd0t3tlQChAfZSkD96Rd9DsmbbSraTuIFP__zn7DCN6RvIQzMc","dq":"QuSqY4my7EpYk4kKnZPm_t7b7jEPzB57FCiTKDz5t9_PXX7BohYD5fN6OoS_9sb22B7cMt20IlqJ0dcdtqcH5iUlCACme1OOkZKTcUcHtcDxBIv1WoGLUROeTE8nIPjj0qmwko5V3FGw2OP3ag3tuhuFPxVPM-mLoPfpWZYnDHE","qi":"cyCVFBj5wM1-5syqt0xVuW5U8w6ZaPMM2F4GlkCu6lb-vNkymgmK3uGvr6p5VpJGU4UsEk1yrH3KrzlBHE4j8ssSMx4CLmq6Hpf4c9zkR8fO8-lgBdhyPmlHyIDvloeJqs4503qhFZO30UMTjVEKL6Wk_83CF00JkOhMo1uyv-A","kid":"38285a37-2843-48f9-a69c-6d72f8c4f016","key_ops":["verify"],"alg":"RS256"},{"kty":"EC","x":"8Whe4H2LCoTN4SODA7GIUrFYD-CpoYS7EvUsbOfcjn8","y":"Vs7VB8ozhyUkSy951Sq4clynrgg8URX91f6FjNDy18k","crv":"P-256","d":"UWPQ4T7opsJhdPbTohO0hf0noTkqGlEWOQrP0l_Tteo","kid":"406f824a-a71b-435d-8e0e-12ee8b07f88b","key_ops":["sign","verify"],"alg":"ES256"},{"crv":"Ed25519","d":"6MooMObDBKW1QRe1uHnrzKoWY9iJKcY55kD1rSY9jiI","x":"4yF8m6gflwZntZMc12j4hIUZFuZ5XJlAqbpnlEIgSxk","kty":"OKP","kid":"a678b12f-0f67-4802-ba7a-a3ad0ea5cc17","key_ops":["verify"],"alg":"EdDSA"}]' ``` The response returned is: ```json { "keys": [ { "alg": "ES256", "crv": "P-256", "kid": "fa6f71ab-03e2-435f-b2f3-9b6143a9c295", "kty": "EC", "use": "sig", "key_ops": ["verify"], "x": "3HcQyhPGXE9Tr_7VMIUvh-PJfQ_nXe_d2Ho7HWefJLA", "y": "CAdc8gjfA8eIwDjWzEdeRurZFUHs_OZ-SEMWcW_UUaE" }, { "alg": "RS256", "e": "AQAB", "kid": "479be47e-ca6e-44cd-a638-b17754611553", "kty": "RSA", "n": "16Tia3phliCATvt0VtISvrWazjIw_BN6a7b5p9VerjoaZfx98l6DJfRrxi2RW6aijPPuzT3DYTfHqNkb1SQUGPKQa_OXGDzPPFXT9RTma24I6vi2VjuLmDPQI6vpAiLqCuBHQ_5gMlyN8wa7F1r86Mf1F-14p_78tBBpea1zSPFcgaODjfOens8Om2CV_eKlK-_zPMCacM96X2Gtx00QVS1UEClQaiZWMvwfdprWfg9w8D2C4ze5wNWIVeMINeO-Ajug3nvhw8UJwZS-ZqiIVD34e3gCukc4bAht-F6xO7RGmOry7UpTe-56r9reaRfpN-W2G2si_sb5zikaJuQcEQ", "use": "sig", "key_ops": ["verify"] } ] } ``` ### JWT changes * Now supports encrypted and verifying using EC, RSA, Ed25519 and HMAC * Change the `use` claim in the JWK from `sig` to `enc` to specify the signing/encryption key - this conforms with the JWK spec. * `GOTRUE_JWT_KEYS` will default to use `GOTRUE_JWT_SECRET` if it's not set * A key can continue to be used for verification as long as it's present in `GOTRUE_JWT_KEYS` * A revoked key is one that is removed from `GOTRUE_JWT_KEYS` * `ValidMethods` are computed on config initialisation so we don't have to do that on every request
🤖 I have created a release *beep* *boop* --- ## [2.157.0](supabase/auth@v2.156.0...v2.157.0) (2024-07-26) ### Features * add asymmetric jwt support ([supabase#1674](supabase#1674)) ([c7a2be3](supabase@c7a2be3)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
What kind of change does this PR introduce?
What is the current behavior?
What is the new behavior?
Config changes
GOTRUE_JWT_KEYS
which takes in an array of JWKGOTRUE_JWT_SECRET
andGOTRUE_JWT_KEY_ID
ifGOTRUE_JWT_KEYS
is missing, which is just the JWK representation of the symmetric secretGOTRUE_JWT_KEYS
is transformed and stored as JWKs in-memory.key_ops
claim in the JWK and to detect if they should be used to sign or verify a JWT (see RFC)key_ops
claim set to["verify"]
, while the JWK represented as private keys will have thekey_ops
claim set to["sign", "verify"]
if it is used for signingEndpoint
GET /.well-known/jwks.json
: returns the JWKs for the auth service. Given the following config (generated from this script):The response returned is:
JWT changes
use
claim in the JWK fromsig
toenc
to specify the signing/encryption key - this conforms with the JWK spec.GOTRUE_JWT_KEYS
will default to useGOTRUE_JWT_SECRET
if it's not setGOTRUE_JWT_KEYS
GOTRUE_JWT_KEYS
ValidMethods
are computed on config initialisation so we don't have to do that on every request