Free Security Assessment
Remote Support
Enter your PIN-code to connect with one of our technicians
For more support options Click here

Ghost OpenID Connect SSO

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.

# SSO Adapter Configuration
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.