diff --git a/internal/start/start.go b/internal/start/start.go index bcc2cde48..1184b6590 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "encoding/json" "fmt" "io" "net" @@ -621,8 +622,8 @@ EOF fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3), } - // Since signing key is validated by ResolveJWKS, simply read the key file. - if keys, err := afero.ReadFile(fsys, utils.Config.Auth.SigningKeysPath); err == nil && len(keys) > 0 { + // Serialise default or custom signing keys + if keys, err := json.Marshal(utils.Config.Auth.SigningKeys); err == nil { env = append(env, "GOTRUE_JWT_KEYS="+string(keys)) // TODO: deprecate HS256 when it's no longer supported env = append(env, "GOTRUE_JWT_VALID_METHODS=HS256,RS256,ES256") diff --git a/pkg/config/apikeys.go b/pkg/config/apikeys.go index 12ecb2076..a5f6ee1de 100644 --- a/pkg/config/apikeys.go +++ b/pkg/config/apikeys.go @@ -4,6 +4,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" "crypto/rsa" "encoding/base64" "math/big" @@ -11,6 +12,8 @@ import ( "github.com/go-errors/errors" "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/supabase/cli/pkg/cast" ) const ( @@ -46,6 +49,25 @@ func (a *auth) generateAPIKeys() error { } else if len(a.JwtSecret.Value) < 16 { return errors.Errorf("Invalid config for auth.jwt_secret. Must be at least 16 characters") } + // Generate default signing key (P-256 curve for ES256) + if len(a.SigningKeysPath) == 0 { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return errors.Errorf("failed to generate ECDSA key: %w", err) + } + a.SigningKeys = append(a.SigningKeys, JWK{ + KeyType: "EC", + KeyID: uuid.New().String(), + Use: "sig", + KeyOps: []string{"sign", "verify"}, + Algorithm: "ES256", + Extractable: cast.Ptr(true), + Curve: "P-256", + X: base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.X.Bytes()), + Y: base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.Y.Bytes()), + PrivateExponent: base64.RawURLEncoding.EncodeToString(privateKey.D.Bytes()), + }) + } // Generate anon key if not provided if len(a.AnonKey.Value) == 0 { signed, err := a.generateJWT("anon") diff --git a/pkg/config/config.go b/pkg/config/config.go index 6e9ef96bb..3c88e0340 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1538,7 +1538,7 @@ func (a *auth) ResolveJWKS(ctx context.Context) (string, error) { jwks.Keys = append(jwks.Keys, json.RawMessage(publicKeyEncoded)) } // Fallback to JWT_SECRET for backward compatibility - if len(a.SigningKeys) == 0 { + if len(a.SigningKeysPath) == 0 { jwtSecret := secretJWK{ KeyType: "oct", KeyBase64URL: base64.RawURLEncoding.EncodeToString([]byte(a.JwtSecret.Value)),