mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
refactor(core): refactor oidc sso connector (#5528)
refactor oidc sso connector
This commit is contained in:
parent
f638c8e6a2
commit
b994f1e2ad
2 changed files with 23 additions and 57 deletions
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
// Decode and verify the id token
|
||||
const { sub, name, picture, email, email_verified, phone, phone_verified } =
|
||||
await getIdTokenClaims(idToken, oidcConfig, nonce);
|
||||
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: sub,
|
||||
id,
|
||||
...conditional(name && { name }),
|
||||
...conditional(picture && { avatar: picture }),
|
||||
...conditional(email && email_verified && { email }),
|
||||
...conditional(phone && phone_verified && { phone }),
|
||||
...camelcaseKeys(rest),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue