Ghost OpenID Connect SSO
Ghost provides a Single Sign-On architecture in the form of SSO adapters, but there is very little information about it in the Ghost documentation.
The only working solution we found is the:
https://github.com/Catzilla/ghost-sso-header
The documentation explains how to install the sso adapter, but if you use docker compose self hosted Ghost , place the following env vars into your .env file.
adapters__sso__active=ghost-sso-header
adapters__sso__ghost-sso-header__header=X-User
adapters__sso__ghost-sso-header__jsonpath=$.email
However, the latest Ghost versions have removed the jsonpath module from the distribution, so you need to modify the index.js code to exclude it. AI helped us write replacement code, as explained in our blog.
We use Nginx Proxy Manager as our reverse proxy, and here is a Microsoft OpenID Connect example showing how to configure it for the SSO adapter.
Location /ghost/
access_by_lua_block {
local cjson = require "cjson"
local openidc = require "resty.openidc"
local opts = {
discovery = "https://login.microsoftonline.com/xxxxxx/v2.0/.well-known/openid-configuration",
client_id = "xxxxxxx",
client_secret = "xxxxxxx",
redirect_uri = "/ghost/callback",
scope = "openid email profile offline_access",
ssl_verify = "no",
-- access_token_expires_in = 3600,
-- refresh_session_interval = 3000,
token_endpoint_auth_method = "client_secret_basic",
access_token_expires_in = 86400
-- Optional: Cache isolation for this location
-- cache_segment = "ghost"
}
local session_opts = {
cookie_secure = true,
cookie_http_only = true,
cookie_same_site = "Lax",
secret = "xxxxxxx",
idling_timeout = 86400,
rolling_timeout = 86400
}
-- Authenticate (handles redirect to Entra if needed)
local res, err = openidc.authenticate(opts, nil, nil, session_opts)
if err then
ngx.status = 403
ngx.header['content-type'] = 'text/html'
ngx.say("Authentication failed: " .. (err or "Unknown error"))
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- Check for required claims
if not res.user or not res.user.email then
ngx.status = 403
ngx.header['content-type'] = 'text/html'
ngx.say("Missing email claim")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- Inject X-User header as JSON for Ghost adapter
local user_json = cjson.encode({email = res.user.email})
ngx.req.set_header("X-User", user_json)
-- Optional: Log for debugging
ngx.log(ngx.INFO, "Authenticated user: " .. res.user.email)
}You must exclude /ghost/api location from the single sign on above otherwise it breaks Ghosts Sign up and Members login functionality.
