immich-app/immich · error
Unexpected profile response, no `sub`
Error message
Unexpected profile response, no `sub`
What it means
After resolving the OIDC profile (either directly from ID token claims or by fetching the userinfo endpoint), getProfileAndOAuthSid requires the standard `sub` (subject) claim to identify the user. If the profile object lacks `sub`, the response cannot be trusted/mapped to a user, so it throws. This guards against malformed or non-conformant OIDC providers.
Solutions
- Ensure the OAuth client requests the `openid` scope so an ID token with `sub` is issued
- Check the provider's userinfo response includes `sub` (test with curl against the userinfo endpoint)
- If using a non-OIDC provider, map its unique user identifier to `sub` before use
- Verify the userinfo fetch (skipSubjectCheck path) targets the correct userinfo_endpoint from discovery
Example fix
// before // scope: 'email profile' // after // scope: 'openid email profile' // ensures ID token with `sub`
Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(userInfoEndpoint, { headers: { Authorization: `Bearer ${accessToken}` } });
const profile = await res.json();
if (!profile?.sub) throw new Error(`Provider userinfo lacks 'sub': ${JSON.stringify(profile).slice(0, 200)}`); Type guard
const isOAuthProfile = (p: unknown): p is { sub: string } =>
typeof p === 'object' && p !== null && 'sub' in p && typeof (p as any).sub === 'string' && (p as any).sub.length > 0; Try / catch
try {
const { profile } = await oauthRepo.getProfileAndOAuthSid(/* ... */);
loginUser(profile);
} catch (e) {
if (e.message.includes('no `sub`')) {
logger.error('OIDC provider did not return a subject claim; check openid scope / provider config', e);
redirectToErrorPage('login-provider-misconfigured');
} else throw e;
} Prevention
- Always include the `openid` scope in the authorization request
- Prefer ID-token claims over userinfo when both are available
- Test new providers against the userinfo endpoint before wiring them in
- Validate the discovery metadata advertises a userinfo_endpoint
When it happens
Trigger: OAuth callback calls getProfileAndOAuthSid(); token claims or the userinfo endpoint return a profile object whose `sub` field is missing, null, or empty; a non-standard provider (e.g. misconfigured scopes omitting the subject) is used.
Common situations: Provider userinfo endpoint returns an error-shaped or partial payload (e.g. only email when scope excludes openid); custom OAuth2 (non-OIDC) provider that does not issue a `sub` claim; scope misconfiguration dropping required claims; provider API version change altering the response shape.
Related errors
- Invalid logout token: it must contain either a sub or a sid…
- Error backchannel logout: token validation failed
- Error in OAuth discovery
- Invalid logout token: no claims found
- Received backchannel logout request but OAuth is not enabled
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/aa6a247f3df70711.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/repositories/oauth.repository.ts:104
codeVerifier: string,
): Promise<{ profile: OAuthProfile; sid?: string; idToken?: string }> {
const client = await this.getClient(config);
const pkceCodeVerifier = client.serverMetadata().supportsPKCE() ? codeVerifier : undefined;
try {
const tokens = await authorizationCodeGrant(client, new URL(url), { expectedState, pkceCodeVerifier });
let profile: OAuthProfile;
const tokenClaims = tokens.claims();
if (tokenClaims && 'email' in tokenClaims) {
this.logger.debug('Using ID token claims instead of userinfo endpoint');
profile = tokenClaims as OAuthProfile;
} else {
profile = await fetchUserInfo(client, tokens.access_token, skipSubjectCheck);
}
if (!profile.sub) {
throw new Error('Unexpected profile response, no `sub`');
}
let sid: string | undefined;
if (tokens.id_token) {
const claims = tokens.claims();
if (typeof claims?.sid === 'string') {
sid = claims.sid;
}
}
return { profile, sid, idToken: tokens.id_token };
} catch (error: Error | any) {
if (error.message.includes('unexpected JWT alg received')) {
this.logger.warn(
[
'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.',
'Or, that you have specified a signing key in your OAuth provider.',
].join(' '),View on GitHub (pinned to e55ac299a4)