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