So, let’s say that I wanted to generate JWTs client side, not on the server. Yes, there are security issues. I know. I’m NOT actually storing the API key client side, which I think addresses the issue.
DISCLAIMER: All the API keys posted below are for a localhost test environment that’s not routable beyond my home router. Never post your real API keys anywhere.
I’m hitting a snag that looks like a problem with JWT generation and what’s supposed to be in the signature. I checked to see if my JWTs validated on jwt.io, and they do, but Ghost doesn’t like them, returning:
"code":"INVALID_JWT","name":"UnauthorizedError","statusCode":401,"level":"normal","message":"Invalid token: invalid signature","stack":"JsonWebTokenError: invalid signature
at authenticateWithToken (D:\\Programming\\client sites\\theme_edits\\versions\\5.26.2\\core\\server\\services\\auth\\api-key\\admin.js:163:29)
at D:\\Programming\\client sites\\theme_edits\\versions\\5.26.2\\node_modules\\jsonwebtoken\\verify.js:133:19
at getSecret (D:\\Programming\\client sites\\theme_edits\\versions\\5.26.2\\node_modules\\jsonwebtoken\\verify.js:90:14)
at Object.module.exports [as verify] (D:\\Programming\\client sites\\theme_edits\\versions\\5.26.2\\node_modules\\jsonwebtoken\\verify.js:94:10)
at authenticateWithToken (D:\\Programming\\client sites\\theme_edits\\versions\\5.26.2\\core\\server\\services\\auth\\api-key\\admin.js:160:17)
I’ve tried base64 encoding and not base64 encoding the signature. It doesn’t seem to matter. I get 401 Unauthorized either way.
Here’s an example JWT-containing header:
Authorization: Ghost eyJhbGciOiJIUzI1NiIsImtpZCI6IjYzNzk4NmY0ODY2MGM4MTY0MDBlYWVlNCIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzMxMDg0ODEsImlhdCI6MTY3MzEwODE4MSwiYXVkIjoiL2FkbWluLyJ9.y4rrPlxHabmD_dfdqGasE6-Q860QJ5GvQ1PvVaNfdqE
Here’s what jwt.io says:
My API key, if anyone would like to check my work:
637986f48660c816400eaee4:c9bda8b3fe395bfeb0d95ec2f1bf21d4872d198dfd3966d5df427178f602f203
Help would be appreciated. I’m at about three hours in and am about ready to pull my hair out on this one.
The code I’m running client side:
<script type="text/javascript" src="/assets/js/crypto-js/crypto-js.js"></script>
<script>
function base64url(source) {
// Encode in classical base64
encodedSource = CryptoJS.enc.Base64.stringify(source);
// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');
// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');
return encodedSource;
}
function makeKey(key) {
const [id, secret] = key.split(':');
var header = {
"alg": "HS256",
"kid": id,
"typ": "JWT"
}
var stringifiedHeader = CryptoJS.enc.Utf8.parse(JSON.stringify(header));
var encodedHeader = base64url(stringifiedHeader);
var data =
{
"exp": Math.floor(Date.now() / 1000) + 300 ,
"iat": Math.floor(Date.now() / 1000),
"aud": "/admin/"
}
var stringifiedData = CryptoJS.enc.Utf8.parse(JSON.stringify(data));
var encodedData = base64url(stringifiedData);
var token = encodedHeader + "." + encodedData;
console.log('secret is',secret)
var signature = CryptoJS.HmacSHA256(token, secret);
signature = base64url(signature);
var signedToken = token + "." + signature;
return signedToken
}
</script>