How to check signature of ghost-members-ssr cookie in js

FWIU This Use Ghost authentication to login to custom app explains how the ghost-members-ssr cookie is hashed with the THEME_SESSION_SECRET to produce the ghost-members-ssr.sig cookie.

I am trying to work out how to check this signature using javascript - but totally bemused.

firstly it says the ghost-members-ssr cookie stores the user’s email - in actual fact it’s a guid not an email. But also my ghost-members-ssr.sig cookie value doesn;t look like an SHA hash to me (it is ‘nkZfmfcS8tw5L4hQm7fS6ntfn3Y’).

Have I got totally the wrong end of the stick here or have things changed…

here is the javascript I am using to check the signature:

import jssha from "jssha";

app.get('/process', asyncHandler(async (req, res) => {
  const shaObj = new jssha("SHA-1", "TEXT", {
    hmacKey: { value:  process.env.THEME_SESSION_SECRET, format: "HEX" },
  shaObj.update( req.cookies['ghost-members-ssr']);
  const hmac = shaObj.getHash("HEX");
    ssr: req.cookies['ghost-members-ssr'],
    sig: req.cookies['ghost-members-ssr.sig'],
    secret: process.env.THEME_SESSION_SECRET
  if (hmac !== req.cookies['ghost-members-ssr.sig']) {
    throw new Error("auth fail");

it doesn’t work… I get:

  hmac: 'a4c38010dfbf3e0af3104b1701b52a190207583f',
  ssr: '-----  redacted -----',
  sig: 'nkZfmfcS8tw5L4hQm7fS6ntfn3Y',
  secret: '--- redacted ----

ghost-members-ssr.sig which contains the base64-urlencoded SHA1-HMAC signature of the ghost-members-ssr cookie

It looks like you’re using the value of the cookie directly expecting it to be an SHA1-HMAC signature but it’s not, it’s that signature base64-urlencoded so you need to decode it first.

However, as you’re using JS you could use the same cookies package that Ghost uses, there are details in the article you referenced under the “Story time” heading.


ok thanks - i will play with that. Also one thing I have realised is that the ssr cookie isn’t the email as the posts suggests - it’s the transient id from the members table…

Thanks so much - a case of RTFM!

here’s working code:

import dotenv from 'dotenv';

import express from 'express';
import asyncHandler from 'express-async-handler';
import cookieParser from 'cookie-parser';
// import validUrl from 'valid-url';
import cookies from 'cookies';
import { method1 } from './src/lib.js';

const app = express();

app.get('/process', asyncHandler(async (req, res) => {
  const cks = cookies(req, res, { cookieSecure: true, keys: [process.env.THEME_SESSION_SECRET] });
  const value = cks.get('ghost-members-ssr', { signed: true });
  if (!value) {
    throw new Error("auth fail");
  console.log({ value });

// Define a custom error handling middleware
app.use((err, req, res, next) => {
    console.error(err); // Log the error for debugging purposes
    // Render a more descriptive error message as a JSON response
    res.status(503).json({ error: `An error occurred: ${err.message}` });

const port = process.env.PORT || 80;

app.listen(port, () => {
    console.log(`Server started on port ${port}`);

1 Like

For reference - the ssr cookie has been changed to be a transient_id not the email and the api does not appear to allow querying by transient id. It turns out all i needed to do was call /members/api/member/ from node using the cookies!