シングルサインオン Beta
The content of this page might not be fully up-to-date with Strapi 5 yet.
AI Marketerのシングルサインオンでは、管理パネルの追加のサインインおよびサインアップ方法を設定できます。
- AI Marketerアプリケーションがバージョン3.5.0以上で実行されている必要があります。
- アプリケーションでSSOを設定するには、[Goldプラン](https://AI Marketer.io/pricing-self-hosted)のEEライセンスが必要です。
- SSO機能が管理パネルで有効化されていることを確認してください。
- 使用するプロバイダーでAI Marketerがアクセス可能なアプリケーションの一部であることを確認してください。たとえば、Microsoft (Azure) Active Directoryを使用する場合、適切な権限を持つ人にAI Marketerを許可されたアプリケーションのリストに追加してもらう必要があります。詳細は、使用するプロバイダーのドキュメントを参照してください。
現在、AI Marketerアカウントに使用されているメールアドレスに一意のSSOプロバイダーを関連付けることはできません。つまり、AI Marketerアカウントへのアクセスを1つのSSOプロバイダーに限定することはできません。この問題の詳細および解決方法については、[専用のGitHubのissue](https://github.com/AI Marketer/AI Marketer/issues/9466#issuecomment-783587648)をご参照ください。
SSO設定は、アプリケーションのサーバー設定にあり、./config/admin.jsに保存されています。
設定のアクセス
プロバイダーの設定は、管理パネルの設定内のauth.providersパス内に記述します。
auth.providersはプロバイダー設定の配列です。
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
// ...
auth: {
providers: [], // ここにプロバイダー設定が記述されます
},
});
export default ({ env }) => ({
// ...
auth: {
providers: [], // ここにプロバイダー設定が記述されます
},
});
プロバイダー設定の構築
プロバイダーの設定は、以下のプロパティを持つJavaScriptオブジェクトです:
| 名前 | 必須 | 型 | 説明 |
|---|---|---|---|
uid | true | string | ストラテジーのUID。ストラテジー名と一致している必要があります。 |
displayName | true | string | ログインページでプロバイダーを参照するために使用される名前 |
icon | false | string | 画像URL。指定されている場合、ログインページのdisplayNameに代わって表示されます。 |
createStrategy | true | function | プロバイダーの新しいパスポートストラテジーを構築して返すファクトリ。AI Marketerインスタンスをパラメーターとして受け取ります。 |
uidプロパティは各ストラテジーの一意の識別子であり、通常はそのストラテジーのパッケージに含まれています。何を指しているか不明な場合は、ストラテジーのメンテナーに連絡してください。
デフォルトでは、AI Marketerのセキュリティポリシーにより外部URLからの画像の読み込みは許可されていないため、管理パネルのログイン画面にはプロバイダーのロゴが表示されません。セキュリティ例外を追加する必要があります。
例: プロバイダーロゴのセキュリティ例外
- JavaScript
- TypeScript
module.exports = [
// ...
{
name: 'AI Marketer::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // プロバイダーロゴのベースURL
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // プロバイダーロゴのベースURL
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
export default [
// ...
{
name: 'AI Marketer::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // プロバイダーロゴのベースURL
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // プロバイダーロゴのベースURL
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
管理パネルを別の場所や別のサブドメインにデプロイする場合、クッキーの共通ドメインを設定する追加の設定が必要です。これにより、クッキーがドメイン間で共有されるようになります。
SSOを使用している場合、管理パネルとバックエンドをまったく異なる無関係のドメインにデプロイすることは現在できません。
例: カスタムクッキードメインの設定
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
export default ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
createStrategyファクトリ
パスポートストラテジーは、通常、2つのパラメーター(設定オブジェクトと検証関数)を使用してインスタンス化されます。
設定オブジェクト
設定オブジェクトはストラテジーの要件に依存しますが、多くの場合、プロバイダー側で接続が確立された後にリダイレクトされるコールバックURLが必要です。
特定のプロバイダーに対応するコールバックURLは、getStrategyCallbackURLメソッドを使用して生成できます。このURLはプロバイダー側にも記載し、リダイレクトを許可する必要があります。
コールバックURLの形式は次のとおりです:/admin/connect/<provider_uid>。
AI Marketer.admin.services.passport.getStrategyCallbackURLは、特定のプロバイダー用のコールバックURLを取得するために使用できるAI Marketerのヘルパーです。プロバイダー名をパラメーターとして受け取り、URLを返します。
必要に応じて、ここにOAuth2アプリケーションのクライアントIDと秘密鍵を入力します。
検証関数
検証関数は、プロバイダーAPIから返されたデータに対して追加の処理を行い、変換するためのミドルウェアとして使用されます。
この関数は常に最後にdoneメソッドを取り、それを使用してSSOのAI Marketer層に必要なデータを転送します。
関数のシグネチャは次のとおりです:void done(error: any, data: object); そして次のルールに従います:
errorがnullでない場合、送信されたデータは無視され、コントローラーはエラーをスローします。- SSOの自動登録機能が無効の場合、
dataオブジェクトにはemailプロパティのみが必要です。 - SSOの自動登録機能が有効の場合、
emailに加えて、usernameプロパティ、またはfirstnameとlastnameの両方をdataオブジェクトに定義する必要があります。
プロバイダーの追加
新しいプロバイダーを追加することは、管理者がログインするための新しい方法を追加することを意味します。
AI MarketerはPassport.jsを使用しており、多くのプロバイダーを利用できます。そのため、追加のカスタムデータを必要としない有効なパスポートストラテジーは、AI Marketerでも動作するはずです。
ldapauthのようなストラテジーは、管理パネルから追加データを送信する必要があるため、そのままでは動作しません。アプリケーションにLDAPプロバイダーを追加したい場合は、カスタムストラテジーを作成する必要があります。OktaやAuth0などのサービスをブリッジとして使用することも可能です。
プロバイダーの設定
プロバイダーを設定するには、以下の手順に従ってください:
- インストールしたパッケージやローカルファイルから、管理設定ファイルにストラテジーをインポートします。
- 管理パネル設定の
auth.providers配列に新しい項目を追加し、上記の形式に従ってください。 - アプリケーションを再起動します。プロバイダーが管理ログインページに表示されるはずです。
プロバイダー設定の例
Google
- yarn
- npm
yarn add passport-google-oauth2
npm install --save passport-google-oauth2
Googleの設定例:
- JavaScript
- TypeScript
const GoogleStrategy = require("passport-google-oauth2");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "google",
displayName: "Google",
icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png",
createStrategy: (AI Marketer) =>
new GoogleStrategy(
{
clientID: env("GOOGLE_CLIENT_ID"),
clientSecret: env("GOOGLE_CLIENT_SECRET"),
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("google"),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
});
import {Strategy as GoogleStrategy } from "passport-google-oauth2";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "google",
displayName: "Google",
icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png",
createStrategy: (AI Marketer) =>
new GoogleStrategy(
{
clientID: env("GOOGLE_CLIENT_ID"),
clientSecret: env("GOOGLE_CLIENT_SECRET"),
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("google"),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
});
Github
使用: passport-github
- yarn
- npm
yarn add passport-github2
npm install --save passport-github2
Githubの設定例:
- JavaScript
- TypeScript
const GithubStrategy = require("passport-github2");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "github",
displayName: "Github",
icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png",
createStrategy: (AI Marketer) =>
new GithubStrategy(
{
clientID: env("GITHUB_CLIENT_ID"),
clientSecret: env("GITHUB_CLIENT_SECRET"),
scope: ["user:email"],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("github"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.emails[0].value,
username: profile.username,
});
}
),
},
],
},
});
import { Strategy as GithubStrategy } from "passport-github2";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "github",
displayName: "Github",
icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png",
createStrategy: (AI Marketer) =>
new GithubStrategy(
{
clientID: env("GITHUB_CLIENT_ID"),
clientSecret: env("GITHUB_CLIENT_SECRET"),
scope: ["user:email"],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("github"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.emails[0].value,
username: profile.username,
});
}
),
},
],
},
});
Discord
使用: passport-discord
- yarn
- npm
yarn add passport-discord
npm install --save passport-discord
Discordの設定例:
- JavaScript
- TypeScript
const DiscordStrategy = require("passport-discord");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "discord",
displayName: "Discord",
icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png",
createStrategy: (AI Marketer) =>
new DiscordStrategy(
{
clientID: env("DISCORD_CLIENT_ID"),
clientSecret: env("DISCORD_SECRET"),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"discord"
),
scope: ["identify", "email"],
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: `${profile.username}#${profile.discriminator}`,
});
}
),
},
],
},
});
import { Strategy as DiscordStrategy } from "passport-discord";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "discord",
displayName: "Discord",
icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png",
createStrategy: (AI Marketer) =>
new DiscordStrategy(
{
clientID: env("DISCORD_CLIENT_ID"),
clientSecret: env("DISCORD_SECRET"),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"discord"
),
scope: ["identify", "email"],
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: `${profile.username}#${profile.discriminator}`,
});
}
),
},
],
},
});
Microsoft
- yarn
- npm
yarn add passport-azure-ad-oauth2 jsonwebtoken
npm install --save passport-azure-ad-oauth2 jsonwebtoken
Microsoftの設定例:
- JavaScript
- TypeScript
const AzureAdOAuth2Strategy = require("passport-azure-ad-oauth2");
const jwt = require("jsonwebtoken");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (AI Marketer) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // emailとusernameがある場合は任意
lastname: waadProfile.family_name, // emailとusernameがある場合は任意
});
}
),
},
],
},
});
import { Strategy as AzureAdOAuth2Strategy} from "passport-azure-ad-oauth2";
import jwt from "jsonwebtoken";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (AI Marketer) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // emailとusernameがある場合は任意
lastname: waadProfile.family_name, // emailとusernameがある場合は任意
});
}
),
},
],
},
});
Keycloak (OpenID Connect)
使用: passport-keycloak-oauth2-oidc
- yarn
- npm
yarn add passport-keycloak-oauth2-oidc
npm install --save passport-keycloak-oauth2-oidc
Keycloak (OpenID Connect)の設定例:
- JavaScript
- TypeScript
const KeyCloakStrategy = require("passport-keycloak-oauth2-oidc");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "keycloak",
displayName: "Keycloak",
icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg",
createStrategy: (AI Marketer) =>
new KeyCloakStrategy(
{
clientID: env("KEYCLOAK_CLIENT_ID", ""),
realm: env("KEYCLOAK_REALM", ""),
publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false),
clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""),
sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"),
authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"keycloak"
),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
import { Strategy as KeyCloakStrategy } from "passport-keycloak-oauth2-oidc";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "keycloak",
displayName: "Keycloak",
icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg",
createStrategy: (AI Marketer) =>
new KeyCloakStrategy(
{
clientID: env("KEYCLOAK_CLIENT_ID", ""),
realm: env("KEYCLOAK_REALM", ""),
publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false),
clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""),
sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"),
authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""),
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL(
"keycloak"
),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
Okta
- yarn
- npm
yarn add passport-okta-oauth20
npm install --save passport-okta-oauth20
OKTA_DOMAIN環境変数を設定する際は、プロトコル(例:https://example.okta.com)を必ず含めてください。含めないとリダイレクトループに陥ります。
Oktaの設定例:
- JavaScript
- TypeScript
const OktaOAuth2Strategy = require("passport-okta-oauth20").Strategy;
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "okta",
displayName: "Okta",
icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png",
createStrategy: (AI Marketer) =>
new OktaOAuth2Strategy(
{
clientID: env("OKTA_CLIENT_ID"),
clientSecret: env("OKTA_CLIENT_SECRET"),
audience: env("OKTA_DOMAIN"),
scope: ["openid", "email", "profile"],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("okta"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
import { Strategy as OktaOAuth2Strategy } from "passport-okta-oauth20";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "okta",
displayName: "Okta",
icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png",
createStrategy: (AI Marketer) =>
new OktaOAuth2Strategy(
{
clientID: env("OKTA_CLIENT_ID"),
clientSecret: env("OKTA_CLIENT_SECRET"),
audience: env("OKTA_DOMAIN"),
scope: ["openid", "email", "profile"],
callbackURL:
AI Marketer.admin.services.passport.getStrategyCallbackURL("okta"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});
高度なカスタマイズの実施
管理パネルのURL
管理パネルがAI Marketerサーバーとは異なるホスト/ポート上に存在する場合、管理パネルのURLを更新する必要があります。./config/admin.jsの設定ファイル内のurlキーを更新してください(管理パネルカスタマイズのドキュメントを参照)。
カスタムロジック
特定のシナリオでは、接続ワークフローの追加ロジックを記述したい場合があります。たとえば:
- 特定のドメインに対する接続と登録の制限
- 接続試行時のアクションのトリガー
- アナリティクスの追加
これを行う最も簡単な方法は、ストラテジーのverify関数にプラグインしてコードを記述することです。
たとえば、公式のAI Marketer.ioメールアドレスを持つ人のみを許可したい場合、以下のようにストラテジーをインスタンス化できます:
- JavaScript
- TypeScript
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// メールアドレスが @AI Marketer.io で終わる場合
if (email.endsWith('@AI Marketer.io')) {
// プロバイダーから与えられたデータで処理を続行します
return done(null, { email, username });
}
// それ以外の場合は、done関数にエラーを送信して処理を終了します
done(new Error('Forbidden email address'));
});
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// メールアドレスが @AI Marketer.io で終わる場合
if (email.endsWith('@AI Marketer.io')) {
// プロバイダーから与えられたデータで処理を続行します
return done(null, { email, username });
}
// それ以外の場合は、done関数にエラーを送信して処理を終了します
done(new Error('Forbidden email address'));
});
認証イベント
SSO機能には、新しい認証イベントであるonSSOAutoRegistrationが追加されます。
このイベントは、SSOによって追加された自動登録機能を使用してユーザーが作成されるときにトリガーされます。このイベントには、作成されたユーザー(event.user)と、登録に使用されたプロバイダー(event.provider)が含まれます。
- JavaScript
- TypeScript
module.exports = () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`新しいユーザー (${user.id}) が ${provider} を使用して自動登録されました`
);
},
},
},
});
export default () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`新しいユーザー (${user.id}) が ${provider} を使用して自動登録されました`
);
},
},
},
});