git-gateway + auth_type: pkce: session never survives reload (restoreUser() disconnected from PKCE login)
Describe the bug
With backend.name: git-gateway and backend.auth_type: pkce, the session never survives a page reload — the user is bounced straight back to the login screen, immediately, every time. This isn't a token-expiry or refresh-timing issue; it's a code path gap: the PKCE login flow and the git-gateway restoreUser() session check are wired to two completely disconnected storage mechanisms, so restoreUser() can never succeed.
Root cause (traced against main)
Login (packages/decap-cms-ui-auth/src/PKCEAuthenticationPage.js, componentDidMount/handleLogin): after PkceAuthenticator completes the code exchange, the component builds a plain object and passes it straight to onLogin:
data.user_metadata = {};
if (data.access_token) {
data.token = data.access_token;
...
}
this.props.onLogin(data);No gotrue-js User instance is created here, and nothing persists a session for gotrue-js to find later.
onLogin → authenticate() (packages/decap-cms-backend-git-gateway/src/implementation.ts:279):
authenticate(credentials: Credentials) {
const user = credentials as GitGatewayUser;
if (user.jwt) {
// Netlify auth
...
} else {
// OAuth
this.tokenPromise = async () => (typeof user.token === 'string' ? user.token : '');
}
...Since the PKCE-built object has .token (a string) but no .jwt, it takes the "OAuth" branch — tokenPromise is a closure over the token string, held only in memory for the life of the GitGatewayClient instance. The User object this method eventually resolves to (and which decap-cms's own authStore persists to localStorage['decap-cms-user']) is built from userData and deliberately excludes the token:
return {
name: userData.name,
login: userData.email,
email: userData.email,
avatar_url: userData.avatar_url,
} as unknown as User;Restore on reload (implementation.ts:389):
async restoreUser() {
const client = await this.getAuthClient();
const user = client.currentUser();
if (!user) return Promise.reject();
return this.authenticate(user as Credentials);
}For the non-widget path, getAuthClient() constructs new GoTrue({ APIUrl: this.apiUrl }), and client.currentUser() calls gotrue-js's User.recoverSession(), which reads gotrue-js's own localStorage key — a key that is only ever written by gotrue-js's own _saveSession() (called from GoTrue.createUser() in the classic user.jwt / Netlify Identity password-grant flow). The PKCE flow above never calls this. So recoverSession() always returns null, currentUser() always returns null, restoreUser() always rejects — deterministically, on every reload, regardless of how much time has passed since login.
To Reproduce
- Configure
config.ymlwith:backend: name: git-gateway auth_type: pkce base_url: <any PKCE-capable git-gateway-compatible auth server> ... - Log in via the PKCE flow, land on the dashboard.
- Reload the page (or close/reopen the tab).
Expected: session is restored, still logged in. Actual: immediately back at the login screen — even 1 second after logging in, with no elapsed-time dependency.
Confirmed live (production reproduction)
localStorage['decap-cms-user']right after login contains only{name, login, avatar_url, backendName}— no token field at all.- On reload, no network request to any token/refresh/git-gateway endpoint is made at all (confirmed via DevTools) — consistent with
restoreUser()failing on a purely synchronous, emptylocalStorageread rather than a failed network refresh. - After the reload,
localStorage['decap-cms-user']is removed entirely (decap-cms's core clears it oncerestoreUser()rejects).
Environment
decap-cms: 3.11.0 (bundle-inspected; same code path present onmainas of this report)- Backend:
git-gateway,auth_type: pkce, via a third-party git-gateway-compatible bridge server (not Netlify's own Identity service) - Browser: Chrome (desktop)
Suggested fix direction
restoreUser()'s session check needs a PKCE-aware branch — either persisting a real session via gotrue-js's own storage when using the OAuth/PKCE token path, or having getAuthClient() know how to recover a token that PKCEAuthenticationPage itself is responsible for persisting (e.g., to localStorage) and reading back on mount, independent of gotrue-js's Identity-Widget-oriented currentUser().
Source: decaporg/decap-cms