Skip to content
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

refactor(core): refactor oidc sso connector #5528

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions packages/core/src/sso/OidcConnector/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { generateStandardId } from '@logto/shared/universal';
import { conditional } from '@silverhand/essentials';
import camelcaseKeys from 'camelcase-keys';
import snakecaseKeys from 'snakecase-keys';

import assertThat from '#src/utils/assert-that.js';

import { SsoConnectorError, SsoConnectorErrorCodes } from '../types/error.js';
import {
type BaseOidcConfig,
type BasicOidcConnectorConfig,
Expand All @@ -13,7 +17,7 @@ import {
type CreateSingleSignOnSession,
} from '../types/session.js';

import { fetchOidcConfig, fetchToken, getIdTokenClaims } from './utils.js';
import { fetchOidcConfig, fetchToken, getIdTokenClaims, getUserInfo } from './utils.js';

/**
* OIDC connector
Expand Down Expand Up @@ -91,8 +95,7 @@ class OidcConnector {
* @param data unknown oidc authorization response
* @param connectorSession The connector session data from the oidc provider session storage
* @returns The user info from the OIDC provider
* @remark Forked from @logto/oidc-connector
*

*/
async getUserInfo(
connectorSession: SingleSignOnConnectorSession,
Expand All @@ -102,18 +105,29 @@ class OidcConnector {
const { nonce, redirectUri } = connectorSession;

// Fetch token from the OIDC provider using authorization code
const { idToken } = await fetchToken(oidcConfig, data, redirectUri);
const { idToken, accessToken } = await fetchToken(oidcConfig, data, redirectUri);

assertThat(
accessToken,
new SsoConnectorError(SsoConnectorErrorCodes.AuthorizationFailed, {
message: 'The access token is missing from the response.',
})
);

// Verify the id token and get the user id
const { sub: id } = await getIdTokenClaims(idToken, oidcConfig, nonce);

// Decode and verify the id token
const { sub, name, picture, email, email_verified, phone, phone_verified } =
await getIdTokenClaims(idToken, oidcConfig, nonce);
// Fetch user info from the userinfo endpoint
const { sub, name, picture, email, email_verified, phone, phone_verified, ...rest } =
await getUserInfo(accessToken, oidcConfig.userinfoEndpoint);

return {
id: sub,
id,
...conditional(name && { name }),
...conditional(picture && { avatar: picture }),
...conditional(email && email_verified && { email }),
...conditional(phone && phone_verified && { phone }),
...camelcaseKeys(rest),
};
}
}
Expand Down
50 changes: 1 addition & 49 deletions packages/core/src/sso/OktaSsoConnector/index.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,12 @@
import { SsoProviderName } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import camelcaseKeys from 'camelcase-keys';

import assertThat from '#src/utils/assert-that.js';

import { fetchToken, getUserInfo, getIdTokenClaims } from '../OidcConnector/utils.js';
import { OidcSsoConnector } from '../OidcSsoConnector/index.js';
import { type SingleSignOnFactory } from '../index.js';
import { SsoConnectorError, SsoConnectorErrorCodes } from '../types/error.js';
import { basicOidcConnectorConfigGuard } from '../types/oidc.js';
import { type ExtendedSocialUserInfo } from '../types/saml.js';
import { type SingleSignOnConnectorSession } from '../types/session.js';

import { logoBase64, logoDarkBase64 } from './consts.js';

export class OktaSsoConnector extends OidcSsoConnector {
/**
* Override the getUserInfo method from the OidcSsoConnector class
*
* @remark Okta's IdToken does not include the sufficient user claims like email_verified, phone_verified, etc. {@link https://devforum.okta.com/t/email-verified-claim/3516/2}
* This method will fetch the user info from the userinfo endpoint instead.
*/
override async getUserInfo(
connectorSession: SingleSignOnConnectorSession,
data: unknown
): Promise<ExtendedSocialUserInfo> {
const oidcConfig = await this.getOidcConfig();
const { nonce, redirectUri } = connectorSession;

// Fetch token from the OIDC provider using authorization code
const { idToken, accessToken } = await fetchToken(oidcConfig, data, redirectUri);

assertThat(
accessToken,
new SsoConnectorError(SsoConnectorErrorCodes.AuthorizationFailed, {
message: 'The access token is missing from the response.',
})
);

// Verify the id token and get the user id
const { sub: id } = await getIdTokenClaims(idToken, oidcConfig, nonce);

// Fetch user info from the userinfo endpoint
const { sub, name, picture, email, email_verified, phone, phone_verified, ...rest } =
await getUserInfo(accessToken, oidcConfig.userinfoEndpoint);

return {
id,
...conditional(name && { name }),
...conditional(picture && { avatar: picture }),
...conditional(email && email_verified && { email }),
...conditional(phone && phone_verified && { phone }),
...camelcaseKeys(rest),
};
}
}
export class OktaSsoConnector extends OidcSsoConnector {}

export const oktaSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.OKTA> = {
providerName: SsoProviderName.OKTA,
Expand Down
Loading