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 a 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.
