Skip to content

feat(core): Add OIDC support for SSO #15988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

RicardoE105
Copy link
Contributor

@RicardoE105 RicardoE105 commented Jun 3, 2025

Summary

Implement OIDC support for single sign on.

Related Linear tickets, Github issues, and Community forum posts

closes https://linear.app/n8n/issue/PAY-2836/spike-implement-support-for-oidc-authentication-method-for-sso

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

@n8n-assistant n8n-assistant bot added core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team labels Jun 3, 2025
@RicardoE105 RicardoE105 force-pushed the pay-2836-spike-implement-support-for-oidc-authentication-method-for branch 2 times, most recently from 05a9b19 to e02da5e Compare June 3, 2025 18:24
@RicardoE105 RicardoE105 changed the title OIDC MVP feat: Add OIDC support Jun 3, 2025
@afitzek afitzek force-pushed the pay-2836-spike-implement-support-for-oidc-authentication-method-for branch 2 times, most recently from 1e58dbc to bb6adbd Compare June 11, 2025 15:42
@afitzek afitzek changed the title feat: Add OIDC support feat(core): Add OIDC support for SSO Jun 12, 2025
@afitzek afitzek force-pushed the pay-2836-spike-implement-support-for-oidc-authentication-method-for branch from 7e3b338 to d8cce48 Compare June 12, 2025 08:25
@afitzek afitzek force-pushed the pay-2836-spike-implement-support-for-oidc-authentication-method-for branch from d8cce48 to 4ec25f1 Compare June 12, 2025 08:33
@afitzek afitzek marked this pull request as ready for review June 12, 2025 10:09
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cubic found 7 issues across 41 files. Review them in cubic.dev

React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai to give specific feedback.


const onSSOLogin = async () => {
const redirectUrl = ssoStore.isDefaultAuthenticationSaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The awaited call to ssoStore.getSSORedirectUrl() is executed before the try / catch block, so any rejection that occurs will bypass the error handler and result in an uncaught promise rejection.

@@ -101,7 +101,7 @@ const onUserAction = (user: IUser, action: string) =>
<N8nActionToggle
v-if="
!user.isOwner &&
user.signInType !== 'ldap' &&
!['ldap'].includes(user.signInType) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OIDC users are not filtered out even though the PR introduces OIDC SSO; consequently action toggles will still be shown for OIDC accounts, likely violating the intended business rule (SSO accounts should be read-only).

Suggested change
!['ldap'].includes(user.signInType) &&
!['ldap', 'oidc'].includes(user.signInType) &&

@@ -72,7 +72,9 @@ const isExternalAuthEnabled = computed((): boolean => {
settingsStore.settings.enterprise.ldap && currentUser.value?.signInType === 'ldap';
const isSamlEnabled =
settingsStore.isSamlLoginEnabled && settingsStore.isDefaultAuthenticationSaml;
return isLdapEnabled || isSamlEnabled;
const isOidcEnabled =
settingsStore.settings.enterprise.oidc && currentUser.value?.signInType === 'oidc';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing settingsStore.settings.enterprise without optional chaining can throw if either settings or enterprise is undefined. Use optional chaining to avoid a runtime crash.

Suggested change
settingsStore.settings.enterprise.oidc && currentUser.value?.signInType === 'oidc';
settingsStore.settings.enterprise?.oidc && currentUser.value?.signInType === 'oidc';

// OIDC
// ----------------------------------------

if (this.licenseState.isOidcLicensed()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any problem while importing or initializing the OIDC module will throw and crash the server because this block is not inside a try-catch, unlike the preceding SAML and subsequent Source Control initializations which handle errors gracefully.

@@ -148,6 +148,7 @@ export class AuthService {
// TODO: Use an in-memory ttl-cache to cache the User object for upto a minute
const user = await this.userRepository.findOne({
where: { id: jwtPayload.id },
relations: ['authIdentities'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveJwt does not use authIdentities, so loading this relation on every authenticated request performs an unnecessary join and increases DB query cost.

@@ -110,7 +110,8 @@ export class User extends WithTimestamps implements IUser, AuthPrincipal {
@AfterLoad()
@AfterUpdate()
computeIsPending(): void {
this.isPending = this.password === null && this.role !== 'global:owner';
this.isPending =
this.password === null && !this.authIdentities?.[0] && this.role !== 'global:owner';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If authIdentities was not explicitly loaded when the entity was fetched, it will be undefined, so the condition !this.authIdentities?.[0] evaluates to true, incorrectly marking users with existing SSO identities as isPending. This can lead to false positives whenever the relation is not eager-loaded or joined in the query.

return await makeRestApiRequest(context, 'POST', '/sso/oidc/config', data);
};

export const initOidcLogin = async (context: IRestApiContext) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exported function is the only one in the file without an explicit return-type annotation, so TypeScript infers Promise<any>. This weakens type-safety for callers and is inconsistent with the rest of the API helpers.

Suggested change
export const initOidcLogin = async (context: IRestApiContext) => {
export const initOidcLogin = async (context: IRestApiContext): Promise<string> => {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants