Your application completed OIDC sign-in, but email is absent or contains a proxy.sudomimus.email address. A successful login proves identity; it does not promise access to the user’s real mailbox address.
For a real email to appear in Sudomimus OIDC UserInfo, the session must have the email scope, the application’s current email policy must permit real data, the user must have granted sharing to that application, and a current primary email must be available. Check those conditions separately.
This guide covers authorization-code login. See OpenID Connect for the integration overview and identity claims and sharing for the complete disclosure rules.
1. Read the correct response with the correct token
An ID Token returned by Sudomimus /token for authorization-code login contains identity and session claims, not email or other profile fields. Refresh ID Tokens also omit those profile fields. A missing email in either ID Token is expected.
Fetch the userinfo_endpoint advertised by OIDC discovery. Send the OIDC access token in the Authorization: Bearer header. Do not send an ID Token or place a token in the query string. Verify that UserInfo sub matches the subject from the ID Token your OIDC library validated.
Connect sessions use Session API UserInfo instead. They do not have OIDC scopes. Mixing the endpoints or token types produces a token error rather than explaining an omitted email. If the response is invalid_token, investigate token expiry, session authority, and the flow that issued the token first.
The default UserInfo format is JSON. If your application registration selects signed UserInfo, use an OIDC library to validate the JWT’s signature, issuer, audience, and subject before reading claims.
2. Check the session’s email scope
Your authorization request should include scope=openid email when email is needed. The application’s OIDC return rule must allow both scopes. profile requests name and avatar claims; it does not request email. A scope outside the allowed list produces invalid_scope.
Sudomimus discovery also advertises claim_state_endpoint. This provider extension returns current policy and grant metadata without profile values. It includes an email entry only when the session has the corresponding scope. An openid-only session returns an empty claims object.
If the email entry is absent, check the original request and start a new interactive authorization with the needed scope. Changing the client configuration does not add a scope to an existing session. Refresh cannot restore a scope previously removed from that session.
3. Check the application’s email policy
In the With portal, open your application’s claim-sharing settings. New applications use placeholder-only email by default.
The following table assumes the session has the email scope:
| Email policy | Expected behavior |
|---|---|
OFF |
Email is omitted, even if the user previously granted it. |
SYNTHETIC_ONLY |
A generated proxy address is returned with email_verified: false. Real email is never requested. |
OPTIONAL |
Real email is returned only when sharing is granted and the current value is available. Otherwise it is omitted. |
REQUIRED |
Completing sign-in or refresh requires a grant and real email data. Use this only when your application needs that information. |
SYNTHETIC_FALLBACK |
Real email is returned when granted and available; otherwise a proxy address is returned with email_verified: false. |
A proxy address is not a verified mailbox. Do not use it as a verified recovery or notification address. Keep your user identity keyed by the validated issuer and sub, not by an email value that can change.
4. Check the user’s current grant and account data
Claim grants belong to one account and one application. Permission to share email with another application does not authorize sharing with yours.
| Grant state | Meaning |
|---|---|
UNKNOWN |
No standing decision exists. Interactive login can ask for a decision when policy requests one. |
DENIED |
The user declined sharing. An optional claim is not automatically requested again on every login. |
GRANTED |
The user agreed to share. Scope, current application policy, and current account data still apply. |
Ask the user to review the application’s entry under Data sharing in their With account. A deliberate interactive request with prompt=consent forces a consent step; it does not override the user’s choice or change a placeholder-only policy. Do not repeat consent requests in a silent loop.
UserInfo reads current policy, grants, and primary email data. Revoking an optional grant stops future real-email disclosure. If the policy permits a real email but no current value exists, the real-only optional claim is omitted. A fallback policy supplies a proxy address. Required data must be completed before a new login or refresh can succeed.
5. Inspect state on your server without logging tokens or email
The following diagnostic assumes the default JSON UserInfo format. Pass the access token from your server session and the subject from a validated ID Token. The result reports presence, verification, policy, and grant state; it does not return the email address.
export async function inspectOidcEmail( accessToken: string, expectedSubject: string,) { const issuer = "https://oidc.sudomimus.com"; const discoveryResponse = await fetch( `${issuer}/.well-known/openid-configuration`, ); if (!discoveryResponse.ok) { throw new Error(`Discovery failed (${discoveryResponse.status})`); } const metadata = await discoveryResponse.json(); if (metadata.issuer !== issuer || !metadata.claim_state_endpoint) { throw new Error("Unexpected issuer or missing claim-state endpoint"); }
const headers = { Authorization: `Bearer ${accessToken}` }; const userInfoResponse = await fetch(metadata.userinfo_endpoint, { headers, cache: "no-store", }); if (!userInfoResponse.ok) { throw new Error(`UserInfo failed (${userInfoResponse.status})`); } if (!userInfoResponse.headers.get("content-type")?.includes("application/json")) { throw new Error("Use an OIDC library to validate signed UserInfo"); } const userInfo = await userInfoResponse.json();
const stateResponse = await fetch(metadata.claim_state_endpoint, { headers, cache: "no-store", }); if (!stateResponse.ok) { throw new Error(`Claim state failed (${stateResponse.status})`); } const state = await stateResponse.json(); if (userInfo.sub !== expectedSubject || state.sub !== expectedSubject) { throw new Error("UserInfo or claim-state subject mismatch"); }
return { emailPresent: typeof userInfo.email === "string", emailVerified: userInfo.email_verified === true, requirement: state.claims.email?.requirement ?? null, grant: state.claims.email?.state ?? null, };}The two responses are live observations, not an atomic snapshot. If settings change between requests, repeat the diagnostic after the user has finished the change. A null requirement is not the same as policy OFF: the email state entry may be absent because of the session’s scope.
Test an openid-only login, optional email declined, optional email granted, placeholder-only email, and an optional grant revoked after login. Confirm that your application handles each result without turning an optional field into an unexplained login error.
For protocol failures, follow OIDC troubleshooting. For a framework-based Connect application instead, see the React Router passkey guide.