[Bug]: Entra ID OIDC not working #879

Open
opened 2026-02-04 21:32:15 +03:00 by OVERLORD · 4 comments
Owner

Originally created by @XenoUniv3rse on GitHub (Dec 5, 2025).

Where is the problem occurring?

I encountered the problem while interacting with the server (Backend)

What browsers are you seeing the problem on?

Brave

Current behavior

I added the var to the docker compose file and I can see the "log in with SSO" button. When I click on the button, i get an "unknown error" and looking at the logs I see ". I dont see any logs in the docker container

Desired behavior

No response

Steps to reproduce

Enable Entra ID SSO and try to log in.

Other information

Here is my env var setup in docker compose file:
- OIDC_ISSUER=https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0
- OIDC_CLIENT_ID=${ENTRA_CLIEN_ID}
- OIDC_CLIENT_SECRET=${ENTRA_SECTRET}
- OIDC_SCOPES=openid email profile
- OIDC_ADMIN_ROLES=${ADMIN_GROUP_ID}
- OIDC_EMAIL_ATTRIBUTE=preferred_username
- OIDC_USERNAME_ATTRIBUTE=preferred_username
- OIDC_ROLES_ATTRIBUTE=groups
- OIDC_ENFORCED=false

Originally created by @XenoUniv3rse on GitHub (Dec 5, 2025). ### Where is the problem occurring? I encountered the problem while interacting with the server (Backend) ### What browsers are you seeing the problem on? Brave ### Current behavior I added the var to the docker compose file and I can see the "log in with SSO" button. When I click on the button, i get an "unknown error" and looking at the logs I see ". I dont see any logs in the docker container ### Desired behavior _No response_ ### Steps to reproduce Enable Entra ID SSO and try to log in. ### Other information Here is my env var setup in docker compose file: - OIDC_ISSUER=https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0 - OIDC_CLIENT_ID=${ENTRA_CLIEN_ID} - OIDC_CLIENT_SECRET=${ENTRA_SECTRET} - OIDC_SCOPES=openid email profile - OIDC_ADMIN_ROLES=${ADMIN_GROUP_ID} - OIDC_EMAIL_ATTRIBUTE=preferred_username - OIDC_USERNAME_ATTRIBUTE=preferred_username - OIDC_ROLES_ATTRIBUTE=groups - OIDC_ENFORCED=false
Author
Owner

@meltyshev commented on GitHub (Dec 5, 2025):

Hi! We currently have limited error reporting on the client side during OIDC authentication. If you don't see any error logs on the server side, it means the failure is happening right after the redirect, and it fails immediately due to missing parameters:

const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);

const state = window.localStorage.getItem('oidc-state');
window.localStorage.removeItem('oidc-state');

const nonce = window.localStorage.getItem('oidc-nonce');
window.localStorage.removeItem('oidc-nonce');

yield put(replace(Paths.LOGIN));

if (params.get('error') !== null) {
  yield put(
    actions.authenticateWithOidc.failure(
      new Error(
        `OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')}`,
      ),
    ),
  );
  return;
}

const code = params.get('code');
if (code === null) {
  yield put(
    actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')),
  );
  return;
}

if (params.get('state') !== state) {
  yield put(
    actions.authenticateWithOidc.failure(
      new Error('Unable to process OIDC response: state mismatch'),
    ),
  );
  return;
}

if (nonce === null) {
  yield put(
    actions.authenticateWithOidc.failure(
      new Error('Unable to process OIDC response: no nonce issued'),
    ),
  );
  return;
}

For now, you can try to determine what's going wrong by inspecting the redirect URL. Open your browser's network tab and check the request right after your provider redirects back. Look at the returned URL and see whether it includes an error parameter (which will explain what happened), or if it's missing required parameters like code or state.

@meltyshev commented on GitHub (Dec 5, 2025): Hi! We currently have limited error reporting on the client side during OIDC authentication. If you don't see any error logs on the server side, it means the failure is happening right after the redirect, and it fails immediately due to missing parameters: ```js const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search); const state = window.localStorage.getItem('oidc-state'); window.localStorage.removeItem('oidc-state'); const nonce = window.localStorage.getItem('oidc-nonce'); window.localStorage.removeItem('oidc-nonce'); yield put(replace(Paths.LOGIN)); if (params.get('error') !== null) { yield put( actions.authenticateWithOidc.failure( new Error( `OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')}`, ), ), ); return; } const code = params.get('code'); if (code === null) { yield put( actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')), ); return; } if (params.get('state') !== state) { yield put( actions.authenticateWithOidc.failure( new Error('Unable to process OIDC response: state mismatch'), ), ); return; } if (nonce === null) { yield put( actions.authenticateWithOidc.failure( new Error('Unable to process OIDC response: no nonce issued'), ), ); return; } ``` For now, you can try to determine what's going wrong by inspecting the redirect URL. Open your browser's network tab and check the request right after your provider redirects back. Look at the returned URL and see whether it includes an `error` parameter (which will explain what happened), or if it's missing required parameters like `code` or `state`.
Author
Owner

@XenoUniv3rse commented on GitHub (Dec 5, 2025):

Hi! We currently have limited error reporting on the client side during OIDC authentication. If you don't see any error logs on the server side, it means the failure is happening right after the redirect, and it fails immediately due to missing parameters:

const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);

const state = window.localStorage.getItem('oidc-state');
window.localStorage.removeItem('oidc-state');

const nonce = window.localStorage.getItem('oidc-nonce');
window.localStorage.removeItem('oidc-nonce');

yield put(replace(Paths.LOGIN));

if (params.get('error') !== null) {
yield put(
actions.authenticateWithOidc.failure(
new Error(
OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')},
),
),
);
return;
}

const code = params.get('code');
if (code === null) {
yield put(
actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')),
);
return;
}

if (params.get('state') !== state) {
yield put(
actions.authenticateWithOidc.failure(
new Error('Unable to process OIDC response: state mismatch'),
),
);
return;
}

if (nonce === null) {
yield put(
actions.authenticateWithOidc.failure(
new Error('Unable to process OIDC response: no nonce issued'),
),
);
return;
}
For now, you can try to determine what's going wrong by inspecting the redirect URL. Open your browser's network tab and check the request right after your provider redirects back. Look at the returned URL and see whether it includes an error parameter (which will explain what happened), or if it's missing required parameters like code or state.

This is what i am seeing

Image

10.11.11.170 is my internal reverse proxy server.

@XenoUniv3rse commented on GitHub (Dec 5, 2025): > Hi! We currently have limited error reporting on the client side during OIDC authentication. If you don't see any error logs on the server side, it means the failure is happening right after the redirect, and it fails immediately due to missing parameters: > > const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search); > > const state = window.localStorage.getItem('oidc-state'); > window.localStorage.removeItem('oidc-state'); > > const nonce = window.localStorage.getItem('oidc-nonce'); > window.localStorage.removeItem('oidc-nonce'); > > yield put(replace(Paths.LOGIN)); > > if (params.get('error') !== null) { > yield put( > actions.authenticateWithOidc.failure( > new Error( > `OIDC Authorization error: ${params.get('error')}: ${params.get('error_description')}`, > ), > ), > ); > return; > } > > const code = params.get('code'); > if (code === null) { > yield put( > actions.authenticateWithOidc.failure(new Error('Invalid OIDC response: no code parameter')), > ); > return; > } > > if (params.get('state') !== state) { > yield put( > actions.authenticateWithOidc.failure( > new Error('Unable to process OIDC response: state mismatch'), > ), > ); > return; > } > > if (nonce === null) { > yield put( > actions.authenticateWithOidc.failure( > new Error('Unable to process OIDC response: no nonce issued'), > ), > ); > return; > } > For now, you can try to determine what's going wrong by inspecting the redirect URL. Open your browser's network tab and check the request right after your provider redirects back. Look at the returned URL and see whether it includes an `error` parameter (which will explain what happened), or if it's missing required parameters like `code` or `state`. This is what i am seeing <img width="978" height="665" alt="Image" src="https://github.com/user-attachments/assets/33f762d6-c4da-4285-87fd-f86c7028e018" /> 10.11.11.170 is my internal reverse proxy server.
Author
Owner

@meltyshev commented on GitHub (Dec 5, 2025):

Hm… Then it's a server-side issue. Could you check what the Response contains? It should include the code and message fields.

From what I see in the code, a 422 status is returned only when the email or name fields are missing from the claims:

const email = _.get(claims, sails.config.custom.oidcEmailAttribute);
const name = _.get(claims, sails.config.custom.oidcNameAttribute);

if (!email || !name) {
  throw 'missingValues';
}

You can also try setting OIDC_CLAIMS_SOURCE to id_token to avoid using the userinfo endpoint and extract everything directly from the token. But it's hard to say which approach will work without seeing the full provider configuration and what the userinfo endpoint is actually returning.

@meltyshev commented on GitHub (Dec 5, 2025): Hm… Then it's a server-side issue. Could you check what the Response contains? It should include the code and message fields. From what I see in the code, a 422 status is returned only when the `email` or `name` fields are missing from the claims: ``` const email = _.get(claims, sails.config.custom.oidcEmailAttribute); const name = _.get(claims, sails.config.custom.oidcNameAttribute); if (!email || !name) { throw 'missingValues'; } ``` You can also try setting `OIDC_CLAIMS_SOURCE` to `id_token` to avoid using the userinfo endpoint and extract everything directly from the token. But it's hard to say which approach will work without seeing the full provider configuration and what the userinfo endpoint is actually returning.
Author
Owner

@XenoUniv3rse commented on GitHub (Dec 5, 2025):

Hm… Then it's a server-side issue. Could you check what the Response contains? It should include the code and message fields.

From what I see in the code, a 422 status is returned only when the email or name fields are missing from the claims:

const email = _.get(claims, sails.config.custom.oidcEmailAttribute);
const name = _.get(claims, sails.config.custom.oidcNameAttribute);

if (!email || !name) {
  throw 'missingValues';
}

You can also try setting OIDC_CLAIMS_SOURCE to id_token to avoid using the userinfo endpoint and extract everything directly from the token. But it's hard to say which approach will work without seeing the full provider configuration and what the userinfo endpoint is actually returning.

Changing OIDC_CLAIMS_SOURCE to id_token did the trick. Thank you

@XenoUniv3rse commented on GitHub (Dec 5, 2025): > Hm… Then it's a server-side issue. Could you check what the Response contains? It should include the code and message fields. > > From what I see in the code, a 422 status is returned only when the `email` or `name` fields are missing from the claims: > > ``` > const email = _.get(claims, sails.config.custom.oidcEmailAttribute); > const name = _.get(claims, sails.config.custom.oidcNameAttribute); > > if (!email || !name) { > throw 'missingValues'; > } > ``` > > You can also try setting `OIDC_CLAIMS_SOURCE` to `id_token` to avoid using the userinfo endpoint and extract everything directly from the token. But it's hard to say which approach will work without seeing the full provider configuration and what the userinfo endpoint is actually returning. Changing OIDC_CLAIMS_SOURCE to id_token did the trick. Thank you
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/planka#879