Reputation: 21685
ensure that the session cookie was signed by the private key corresponding to the token's kid claim. Get the public key from https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys and use a JWT library to verify the signature.
// middleware.js
import { NextResponse } from 'next/server'
import * as jose from 'jose'
export async function middleware(request) {
// Check the cookies
const allCookies = request.cookies.getAll()
console.log(allCookies)
// Get the sessionCookie (if it exists)
const sessionCookie = request.cookies.get("sessionCookie")
console.log("sessionCookie", sessionCookie)
// Test verification using a hard-coded public key from
// https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys
const alg = 'RS256'
const spki = `-----BEGIN CERTIFICATE-----
MIIDHDCCAgSgAwIBAgIEIhgI5zANBgkqhkiG9w0BAQsFADAzMQ8wDQYDVQQDEwZH
aXRraXQxEzARBgNVBAoTCkdvb2dsZSBJbmMxCzAJBgNVBAYTAlVTMB4XDTE5MDEx
NDIzMjgzMVoXDTIwMDEwOTIzMjgzMVowMzEPMA0GA1UEAxMGR2l0a2l0MRMwEQYD
VQQKEwpHb29nbGUgSW5jMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMLh7U8PNsOebb1HVLDa81W/CZK8DrdYl9Vlwj/0/GF7kfLP
zF4qyShw1zAZddlzt7f6lDy2ZvmQ1nqW0IRy8xgYTeAb6aCLY+rm4DEnwJOCnAVo
m1xpgcOVExsXXpleWsP0MugM5xa91Y79CYkVTevPZThgjqfGjan3GiXLQwIJTLG8
xEXXSzmMDeW6dP6CBVPsbbTYSat6CX2nBm2YoJ4v2dij3DQBzOD/d5WLaYL4yE9p
TjmreitFvs1r0AZF53Cq5ju7M10indVyk1zwfK+tuk7VnCZz/mBHEPxT9+Q5EWFi
lVVkZ+2Qau0qRIvar/1QukVpyEsVHPZH+aZ9ilMCAwEAAaM4MDYwDAYDVR0TAQH/
BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCB4AwDQYJ
KoZIhvcNAQELBQADggEBAH4ca9fhlBd9XmaX9E0rZ8QCWT8AnxcVLY+S+nHJg2IT
dj7ZytNJWttKEssOdslrQyamxt2SdQSfpd2UpA+fCO2sRSJOBEA3squGrj1yh1Pt
lajf2TM1MwpdO7ZqBbfE3hLH7srBbVXHxKsUemepxviC+dpF0u4o3EjqHpAvkruO
4RtC4grbFBNRIbBTu7oJHpKnBJWxK7w11a5Sabs6reAuFh68QaVAQzrscYP/M4i9
SPwArWKoCJgrxxVof1N1cal4UpziEH9OaWa7RfyZTYAeEJ/atDo5s8AACPUH542f
JEwmwAJKbJKLRoFYJJXCPeKp1Q7EXQHJNNdS2ABjw+w=
-----END CERTIFICATE-----`
const publicKey = await jose.importSPKI(spki, alg)
const { payload, protectedHeader } = await jose.jwtVerify(sessionCookie.value, publicKey)
console.log(protectedHeader)
console.log(payload)
}
This produces the error
error "spki" must be SPKI formatted string
How do I make this work?
Why does https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys provide not one, but five different public keys?
Each public key appears to have a unique (key, value) pair. For example
"skIBNg": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIB...vb2dsZSBJbmMxCzAJBgNVBAYTA.f+9Vg=\n-----END CERTIFICATE-----\n"
What is the purpose of the key "skIBNg"
? I'm not currently using it.
Upvotes: 1
Views: 450
Reputation: 21685
The JWT header includes a kid like skIBNg
. This kid tells you which one of the five public keys to use.
Furthermore, I needed to use jose.importX509()
, not jose.importSPKI()
. (The giveaway was BEGIN CERTIFICATE
in the string as opposed to BEGIN PUBLIC KEY
).
Here's some incomplete code to make this work.
import { NextResponse } from "next/server";
import * as jose from "jose";
export async function middleware(request) {
// Get the sessionCookie (if it exists)
const sessionCookie = request.cookies.get("sessionCookie");
// Decode header
const protectedHeader = jose.decodeProtectedHeader(sessionCookie.value)
// Decode payload
// const claims = jose.decodeJwt(sessionCookie.value)
// Make sure the alg header is RS256
// Make sure the kid header is in the public_keys keys
if (!(protectedHeader.alg == "RS256" && protectedHeader.hasOwnProperty('kid'))) {
NextResponse.redirect(new URL('/signin', request.url))
}
// Fetch public keys
const publicKeysResponse = await fetch("https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys")
const publicKeysData = await publicKeysResponse.json()
// Get the correct public key
const x509 = publicKeysData[protectedHeader.kid]
const { alg } = protectedHeader
// Validate and fetch the payload
const ecPublicKey = await jose.importX509(x509, alg)
const { payload } = await jose.jwtVerify(sessionCookie.value, ecPublicKey)
// ...
}
Upvotes: 2