0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

feat(console): add standard OIDC and SAML guides (#4982)

* feat(console): add standard OIDC and SAML guides

add standard OIDC and SAML guides

* fix(console): remove console log

remove console log

* chore(console): update settings tab name to experience

update settings tab name to experience
This commit is contained in:
simeng-li 2023-11-28 16:41:52 +08:00 committed by GitHub
parent 14855cde2c
commit 2c038b25ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 377 additions and 5 deletions

View file

@ -0,0 +1,45 @@
import OidcCallbackUri from '@/mdx-components/OidcCallbackUri';
# OIDC single sign-on integration guide
This standard OIDC Single Sign-On (SSO) connector will help you to enable single sign-on (SSO) in your application using Logto. With minimal configuration efforts, this connector allows integration with OIDC-based Identity Provider (IdP).
## Step 1: Create an OIDC application on your IdP
Initiate the OIDC SSO integration by creating an application on the IdP side. You will need to provide the following configurations from the Logto server.
- **Callback URI**: The Logto Callback URI, also known as the Redirect URI or Reply URL, is a specific endpoint or URL that the IdP uses to redirect the user's browser after successful authentication. After a user successfully authenticates with the IdP, the IdP redirects the user's browser back to this designated URI along with an authorization code. Logto will complete the authentication process based on authorization code received from this URI.
<OidcCallbackUri />
Fill in the Logto Callback URI in your IdP OIDC application and continue to create the application.(Most of the OIDC IdPs provides a wide range of application types to choose from. To create a web based SSO connector on Logto, please choose the `Web Application` type.)
<br />
## Step 2: Configure OIDC SSO on Logto
After successfully creating an OIDC application on the IdP side, you will need to provide the IdP configurations back to Logto. Navigate to the `Connection` tab, and fill in the following configurations:
- **Client ID**: A unique identifier assigned to your OIDC application by the IdP. This identifier is used by Logto to identify and authenticate the application during the OIDC flow.
- **Client Secret**: A confidential secret shared between Logto and the IdP. This secret is used to authenticate the OIDC application and secure the communication between Logto and the IdP.
- **Issuer**: The issuer URL, a unique identifier for the IdP, specifying the location where the OIDC identity provider can be found. It is a crucial part of the OIDC configuration as it helps Logto discover the necessary endpoints.
To make the configuration process easier. Logto will automatically fetch the required OIDC endpoints and configurations. This is done by utilizing the issuer you provided and making a call to the IdP's OIDC discover endpoints. It is imperative to ensure that the issuer endpoint is valid and accurately configured to enable Logto to retrieve the required information correctly.
After a successful fetch request, you should be able to see the parsed IdP configurations under the issuers section.
- **Scope**: A space-separated list of strings defining the desired permissions or access levels requested by Logto during the OIDC authentication process. The scope parameter allows you to specify what information and access Logto is requesting from the IdP.
The scope parameter is optional. Regardless of the custom scope settings, Logto will always send the `openid`, `profile` and `email` scopes to the IdP.
This is to ensure that Logto can retrieve the user's identity information and email address properly from the IdP. You may add additional scopes to the scope parameter to request for more information from the IdP.
<br />
## Step 3: Set email domains and enable the SSO connector
Provide the email domains of your organization on the connector experience tab. This will enabled the SSO connector as an authentication method for those users.
Users with email addresses in the specified domains will be restricted to use your SSO connector as their only authentication method.

View file

@ -0,0 +1,57 @@
import SsoSamlSpMetadata from '@/mdx-components/SsoSamlSpMetadata';
# SAML single sign-on integration guide
This standard SAML Single Sign-On (SSO) connector will help you to enable single sign-on (SSO) in your application using Logto. With minimal configuration efforts, this connector allows integration with any SAML-based Identity Provider (IdP).
## Step 1: Create an SAML SSO application on your IdP
Initiate the SAML SSO integration by creating an application on the IdP side. Obtain the following configurations from Logto, representing your Service Provider (SP):
- **Audience URI(SP Entity ID)**: It represents as a globally unique identifier for your Logto service, functioning as the EntityId for SP during authentication requests to the IdP. This identifier is pivotal for the secure exchange of SAML assertions and other authentication-related data between the IdP and Logto.
- **ACS URL**: The Assertion Consumer Service (ACS) URL is the location where the SAML assertion is sent with a POST request. This URL is used by the IdP to send the SAML assertion to Logto. It acts as a callback URL where Logto expects to receive and consume the SAML response containing the user's identity information.
<SsoSamlSpMetadata />
Fill in the above configurations in your IdP SAML application and continue to retrieve the following configurations from your IdP.
<br />
## Step 2: Configure SAML SSO on Logto
To make the SAML SSO integration work, you will need to provide the IdP metadata to Logto. The IdP metadata is an XML document that contains all the information required for Logto to establish the trust with the IdP.
Navigate to the `Connection` tab. Logto provides three different ways to configure the IdP metadata:
1. **Metadata URL**: Provide the URL of the IdP metadata XML document. Logto will fetch the metadata from the URL and configure the SAML SSO integration automatically.
2. **Upload Metadata**: Upload the IdP metadata XML document. Logto will parse the XML document and configure the SAML SSO integration automatically.
3. **Manual Configuration**: Manually configure the IdP metadata.
- IdP entity ID: The Entity ID of the IdP.
- Single sign-on URL: The URL of the IdP Single Sign-On Service.
- Signing certificate: The x509 certificate used to verify the signature of the SAML response from the IdP.
With either of the above configurations, Logto will parse the IdP metadata and configure the SAML SSO integration accordingly.
<br />
## Step 3: Configure user attributes mapping
The user attributes returned from IdP may vary depending on the IdP configuration. Logto provides a flexible way to map the user attributes returned from IdP to the user attributes in Logto. You can configure the user attributes mapping in the SAML SSO integration experience tab.
For minimum configuration, please make sure the following attributes are well mapped either on the IdP side or Logto side:
- id: The unique identifier of the user. Logto will read the `nameId` claim from the SAML response as the user id by default. You may leave this field as default unless you want to use a different claim.
- email: The email address of the user.
<br />
## Step 4: Set email domains and enable the SSO connector
Provide the email domains of your organization in the SAML SSO integration experience tab. This will enable the SSO connector as an authentication method for those users.
Users with email addresses in the specified domains will be restricted to use SAML SSO connector as their only authentication method.

View file

@ -0,0 +1,12 @@
import { SsoProviderName } from '@logto/schemas';
import { type MDXProps } from 'mdx/types';
import { lazy, type LazyExoticComponent, type FunctionComponent } from 'react';
export type GuideComponentType = LazyExoticComponent<FunctionComponent<MDXProps>>;
const ssoConnectorGuides: Readonly<{ [key in SsoProviderName]?: GuideComponentType }> = {
[SsoProviderName.SAML]: lazy(async () => import('./SAML/README.mdx')),
[SsoProviderName.OIDC]: lazy(async () => import('./OIDC/README.mdx')),
};
export default ssoConnectorGuides;

View file

@ -0,0 +1,21 @@
import { type SsoConnectorWithProviderConfig } from '@logto/schemas';
import { createContext, useMemo, type ReactNode } from 'react';
export const SsoConnectorContext = createContext<{
ssoConnector?: SsoConnectorWithProviderConfig;
}>({});
type Props = {
children: ReactNode;
ssoConnector: SsoConnectorWithProviderConfig;
};
function SsoConnectorContextProvider({ children, ssoConnector }: Props) {
const contextValue = useMemo(() => ({ ssoConnector }), [ssoConnector]);
return (
<SsoConnectorContext.Provider value={contextValue}>{children}</SsoConnectorContext.Provider>
);
}
export default SsoConnectorContextProvider;

View file

@ -0,0 +1,9 @@
@use '@/scss/underscore' as _;
.copyToClipboard {
display: block;
}
.inputField {
margin: _.unit(4) 0;
}

View file

@ -0,0 +1,36 @@
import { useContext } from 'react';
import { AppDataContext } from '@/contexts/AppDataProvider';
import { SsoConnectorContext } from '@/contexts/SsoConnectorContextProvider';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import FormField from '@/ds-components/FormField';
import useCustomDomain from '@/hooks/use-custom-domain';
import * as styles from './index.module.scss';
function OidcCallbackUri() {
const { ssoConnector } = useContext(SsoConnectorContext);
const { tenantEndpoint } = useContext(AppDataContext);
const { applyDomain: applyCustomDomain } = useCustomDomain();
if (!ssoConnector) {
return null;
}
const { id } = ssoConnector;
return (
<FormField
title="enterprise_sso.basic_info.oidc.redirect_uri_field_name"
className={styles.inputField}
>
<CopyToClipboard
className={styles.copyToClipboard}
variant="border"
value={applyCustomDomain(new URL(`/callback/${id}`, tenantEndpoint).toString())}
/>
</FormField>
);
}
export default OidcCallbackUri;

View file

@ -0,0 +1,9 @@
@use '@/scss/underscore' as _;
.copyToClipboard {
display: block;
}
.inputField {
margin: _.unit(4) 0;
}

View file

@ -0,0 +1,69 @@
import { useContext, useMemo } from 'react';
import { z } from 'zod';
import { SsoConnectorContext } from '@/contexts/SsoConnectorContextProvider';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import FormField from '@/ds-components/FormField';
import * as styles from './index.module.scss';
// Basic SAML SP config metadata, @see `packages/core/sso/src/types/saml`
const samlServiceProviderMetadataGuard = z.object({
entityId: z.string().min(1),
assertionConsumerServiceUrl: z.string().min(1),
});
// SAML provider config, @see `packages/core/sso/src/SamlSsoConnector`
const samlProviderConfigGuard = z.object({
serviceProvider: samlServiceProviderMetadataGuard,
});
function SsoSamlSpMetadata() {
const { ssoConnector } = useContext(SsoConnectorContext);
const serviceProviderMetadata = useMemo(() => {
if (!ssoConnector) {
return;
}
const { providerConfig } = ssoConnector;
const result = samlProviderConfigGuard.safeParse(providerConfig);
if (!result.success) {
return;
}
return result.data.serviceProvider;
}, [ssoConnector]);
if (!ssoConnector) {
return null;
}
return (
<div>
<FormField
title="enterprise_sso.basic_info.saml.audience_uri_field_name"
className={styles.inputField}
>
<CopyToClipboard
className={styles.copyToClipboard}
variant="border"
value={serviceProviderMetadata?.entityId ?? ''}
/>
</FormField>
<FormField
title="enterprise_sso.basic_info.saml.acs_url_field_name"
className={styles.inputField}
>
<CopyToClipboard
className={styles.copyToClipboard}
variant="border"
value={serviceProviderMetadata?.assertionConsumerServiceUrl ?? ''}
/>
</FormField>
</div>
);
}
export default SsoSamlSpMetadata;

View file

@ -0,0 +1,55 @@
@use '@/scss/underscore' as _;
@use '@/scss/dimensions' as dim;
.content {
flex: 1;
position: relative;
h3 {
font: var(--font-title-2);
color: var(--color-text-secondary);
margin: _.unit(6) 0 _.unit(3);
}
p {
font: var(--font-body-2);
margin: _.unit(4) 0;
}
ul > li,
ol > li {
font: var(--font-body-2);
margin-block: _.unit(2);
padding-inline-start: _.unit(1);
}
table {
border-spacing: 0;
border: 1px solid var(--color-border);
font: var(--font-body-2);
tr {
width: 100%;
}
td,
th {
padding: _.unit(2) _.unit(4);
}
thead {
font: var(--font-title-3);
}
tbody td {
border-top: 1px solid var(--color-border);
}
}
code:not(pre > code) {
background: var(--color-layer-2);
font: var(--font-body-2);
padding: _.unit(1);
border-radius: 4px;
}
}

View file

@ -0,0 +1,53 @@
import { type SsoConnectorWithProviderConfig } from '@logto/schemas';
import { MDXProvider } from '@mdx-js/react';
import classNames from 'classnames';
import { Suspense } from 'react';
import ssoConnectorGuides from '@/assets/docs/single-sign-on';
import SsoConnectorContextProvider from '@/contexts/SsoConnectorContextProvider';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import TextLink from '@/ds-components/TextLink';
import NotFound from '@/pages/NotFound';
import * as styles from './index.module.scss';
type Props = {
ssoConnector?: SsoConnectorWithProviderConfig;
className?: string;
};
function SsoGuide({ ssoConnector, className }: Props) {
if (!ssoConnector) {
return <NotFound />;
}
const { providerName } = ssoConnector;
const Guide = ssoConnectorGuides[providerName];
if (!Guide) {
return <NotFound />;
}
return (
<SsoConnectorContextProvider ssoConnector={ssoConnector}>
<OverlayScrollbar className={classNames(styles.content, className)}>
<MDXProvider
components={{
a: ({ children, ...props }) => (
<TextLink {...props} target="_blank" rel="noopener noreferrer">
{children}
</TextLink>
),
}}
>
<Suspense>
<Guide />
</Suspense>
</MDXProvider>
</OverlayScrollbar>
</SsoConnectorContextProvider>
);
}
export default SsoGuide;

View file

@ -1,19 +1,19 @@
import { withAppInsights } from '@logto/app-insights/react';
import { type SsoProviderName } from '@logto/schemas';
import { pick } from '@silverhand/essentials';
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useLocation, useParams } from 'react-router-dom';
import useSWR, { useSWRConfig } from 'swr';
import ssoConnectorGuides, { type GuideComponentType } from '@/assets/docs/single-sign-on';
import Delete from '@/assets/icons/delete.svg';
import File from '@/assets/icons/file.svg';
import DetailsPage from '@/components/DetailsPage';
import DetailsPageHeader from '@/components/DetailsPage/DetailsPageHeader';
import Skeleton from '@/components/DetailsPage/Skeleton';
import Drawer from '@/components/Drawer';
import Markdown from '@/components/Markdown';
import PageMeta from '@/components/PageMeta';
import { EnterpriseSsoDetailsTabs } from '@/consts';
import ConfirmModal from '@/ds-components/ConfirmModal';
@ -29,6 +29,7 @@ import { type SsoConnectorWithProviderConfigWithGeneric } from '../EnterpriseSso
import Connection from './Connection';
import Experience from './Experience';
import SsoGuide from './SsoGuide';
import * as styles from './index.module.scss';
const enterpriseSsoPathname = '/enterprise-sso';
@ -89,6 +90,13 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
}
};
const ConnectorGuide = useMemo<GuideComponentType | undefined>(() => {
if (!ssoConnector) {
return;
}
return ssoConnectorGuides[ssoConnector.providerName];
}, [ssoConnector]);
if (!ssoConnectorId) {
return null;
}
@ -144,9 +152,7 @@ function EnterpriseSsoConnectorDetails<T extends SsoProviderName>() {
}}
>
{/* TODO: @darcyYe Add SSO connector README. */}
<Markdown className={styles.readme}>
{'# SSO connector guide\n\nThis is a guide for Logto Enterprise SSO connector.'}
</Markdown>
<SsoGuide ssoConnector={ssoConnector} className={styles.readme} />
</Drawer>
<TabNav>
<TabNavItem