-
Notifications
You must be signed in to change notification settings - Fork 384
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add asymmetric jwt support (#1674)
## 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
- Loading branch information
1 parent
f0a40c5
commit c7a2be3
Showing
13 changed files
with
583 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package api | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/lestrrat-go/jwx/v2/jwa" | ||
jwk "github.com/lestrrat-go/jwx/v2/jwk" | ||
) | ||
|
||
type JwksResponse struct { | ||
Keys []jwk.Key `json:"keys"` | ||
} | ||
|
||
func (a *API) Jwks(w http.ResponseWriter, r *http.Request) error { | ||
config := a.config | ||
resp := JwksResponse{ | ||
Keys: []jwk.Key{}, | ||
} | ||
|
||
for _, key := range config.JWT.Keys { | ||
// don't expose hmac jwk in endpoint | ||
if key.PublicKey == nil || key.PublicKey.KeyType() == jwa.OctetSeq { | ||
continue | ||
} | ||
resp.Keys = append(resp.Keys, key.PublicKey) | ||
} | ||
|
||
w.Header().Set("Cache-Control", "public, max-age=600") | ||
return sendJSON(w, http.StatusOK, resp) | ||
} |
Oops, something went wrong.