複数のカスタムドメインを使用する場合は、認証時に適切なカスタムドメインを使用するよう、Auth0 SDK を設定する必要があります。このガイドでは、さまざまなプラットフォームやシナリオにおける SDK の設定方法について説明します。
すべての Auth0 SDK では、認証に使用する Auth0 ドメインを指定する domain パラメーターが必要です。カスタムドメインを使用する場合は、このパラメーターに Auth0 の標準ドメインではなく、カスタムドメインを設定します。
カスタムドメインを使用しない場合:
domain: 'tenant.auth0.com'
カスタムドメインを使用する場合:
domain: 'login.example.com'
カスタムドメインを使用している場合、トークンの iss (発行元) クレームには、カスタムドメインが設定されます。
{
"iss": "https://login.example.com/",
"sub": "auth0|123456",
"aud": "your-client-id"
}
トークン検証で、カスタムドメインを有効な発行元として受け入れるように設定する必要があります。
MCD を使用する場合、すべてのカスタムドメインの提供と検証はお客様の責任となります。SDK でドメインリゾルバー関数を使用してテナントのカスタムドメインを解決するよう設定する場合は、解決されるすべてのドメインが信頼できるものであることをお客様自身で必ず確認してください。ドメインリゾルバーの設定を誤ると、relying party で認証がバイパスされたり、アプリケーションがサーバーサイドリクエストフォージェリにさらされたりする可能性があります。ドメインおよびプロキシサーバーを適切に設定しないと、重大なセキュリティ脆弱性が生じるおそれがあり、これについて Okta は責任を負いません。
Auth0 SPA SDK (JavaScript)
Auth0 SPA SDK を使用するシングルページアプリケーションの場合は、次のように設定します。
Auth0 Next.js SDK (v4+) を使用する Next.js アプリケーションの場合:
Next.js で MCD を使用する際の主な概念:
- 単一の Auth0 テナント、複数のドメイン: すべてのカスタムドメインは同じ Auth0 テナントに属するため、同じ
clientId と clientSecret を共有します。
- DomainResolver 関数:
domain パラメータには、関数 (config: { headers: Headers; url?: URL }) => Promise<string> | string を指定できます。これにより、受信したリクエストヘッダーに基づいて、リクエストごとに動的にドメインを解決できます。
- インスタンスキャッシュ: SDK は、パフォーマンス向上のために、制限付き LRU キャッシュ (最大 100 エントリ) を使用して、ドメインごとに
Auth0Client インスタンスを自動的にキャッシュします。
- セッションの分離: あるカスタムドメイン経由で作成されたセッションはそのドメインに限定されるため、別のドメインのセッションと相互に使用することはできません。
- URL パラメータ: リゾルバ内の
url パラメータは、Server Components と Server Actions では undefined です。使用できるのは middleware または API ルート内のみです。
- ディスカバリキャッシュの調整:
discoveryCache オプションを使用して OIDC メタデータのキャッシュを設定します。
const auth0 = new Auth0Client({
// ... 他の設定
discoveryCache: {
ttl: 600, // 10 分間キャッシュする(デフォルト)
maxEntries: 100 // キャッシュされる issuer の最大数(デフォルト: 100、LRU による削除)
}
});
React アプリケーションで Auth0 React SDK を使用する場合:
import { Auth0Provider } from '@auth0/auth0-react';
function App() {
return (
<Auth0Provider
domain="login.example.com"
clientId="YOUR_CLIENT_ID"
authorizationParams={{
redirect_uri: window.location.origin
}}
>
<MyApp />
</Auth0Provider>
);
}
複数ドメインの構成では:
import { Auth0Provider } from '@auth0/auth0-react';
function App() {
// 環境またはコンテキストに基づいてカスタムドメインを決定する
const auth0Domain = process.env.REACT_APP_AUTH0_DOMAIN || 'login.example.com';
return (
<Auth0Provider
domain={auth0Domain}
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: window.location.origin
}}
>
<MyApp />
</Auth0Provider>
);
}
Auth0.js を使用するアプリケーションの場合:
const webAuth = new auth0.WebAuth({
domain: 'login.example.com',
clientID: 'YOUR_CLIENT_ID',
redirectUri: window.location.origin + '/callback',
responseType: 'code',
scope: 'openid profile email'
});
// ログインを開始
webAuth.authorize();
express-openid-connect を使用する Node.js アプリケーションでは、
const { auth } = require('express-openid-connect');
app.use(
auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: 'https://login.example.com', // カスタムドメイン
baseURL: 'http://localhost:3000',
clientID: 'YOUR_CLIENT_ID',
secret: 'YOUR_CLIENT_SECRET'
})
);
マルチテナントのシナリオでは:
const { auth } = require('express-openid-connect');
// リクエストごとにカスタムドメインを決定するミドルウェア
app.use((req, res, next) => {
// サブドメイン、パス、またはヘッダーからテナント識別子を抽出する
const tenant = req.subdomains[0] || 'default';
// テナントをカスタムドメインにマッピングする
const domainMap = {
'customer1': 'login.customer1.com',
'customer2': 'login.customer2.com',
'default': 'login.example.com'
};
req.auth0Domain = domainMap[tenant] || domainMap.default;
next();
});
// 動的な認証設定
app.use((req, res, next) => {
auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: `https://${req.auth0Domain}`,
baseURL: req.protocol + '://' + req.get('host'),
clientID: process.env.AUTH0_CLIENT_ID,
secret: process.env.AUTH0_CLIENT_SECRET
})(req, res, next);
});
Auth0.swiftを使用する場合:
import Auth0
let auth0 = Auth0
.webAuth(clientId: "YOUR_CLIENT_ID", domain: "login.example.com")
auth0
.scope("openid profile email")
.start { result in
switch result {
case .success(let credentials):
print("Obtained credentials: \(credentials)")
case .failure(let error):
print("Failed with: \(error)")
}
}
ドメインを動的に選択する場合:
import Auth0
class AuthService {
private let clientId = "YOUR_CLIENT_ID"
func getAuth0Domain() -> String {
// アプリの設定に基づいてドメインを決定する
if let savedDomain = UserDefaults.standard.string(forKey: "auth0Domain") {
return savedDomain
}
return "login.example.com" // デフォルト
}
func login(completion: @escaping (Result<Credentials, Error>) -> Void) {
Auth0
.webAuth(clientId: clientId, domain: getAuth0Domain())
.scope("openid profile email")
.start { result in
completion(result)
}
}
}
Auth0.Android を使用する場合は:
import com.auth0.android.Auth0
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.provider.WebAuthProvider
import com.auth0.android.result.Credentials
val account = Auth0.getInstance(
"YOUR_CLIENT_ID",
"login.example.com" // カスタムドメイン
)
WebAuthProvider.login(account)
.withScheme("demo")
.withScope("openid profile email")
.start(this, object : Callback<Credentials, AuthenticationException> {
override fun onSuccess(credentials: Credentials) {
// 成功時の処理
}
override fun onFailure(exception: AuthenticationException) {
// 失敗時の処理
}
})
複数ドメインをサポートするには:
class AuthManager(private val context: Context) {
private val clientId = "YOUR_CLIENT_ID"
private fun getAuth0Domain(): String {
// 共有プリファレンスまたはアプリ設定から取得
val prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE)
return prefs.getString("auth0_domain", "login.example.com") ?: "login.example.com"
}
fun login(callback: Callback<Credentials, AuthenticationException>) {
val account = Auth0.getInstance(clientId, getAuth0Domain())
WebAuthProvider.login(account)
.withScheme("demo")
.withScope("openid profile email")
.start(context, callback)
}
}
react-native-auth0 を使用する場合は、次のとおりです。
import Auth0 from 'react-native-auth0';
const auth0 = new Auth0({
domain: 'login.example.com',
clientId: 'YOUR_CLIENT_ID'
});
// ログイン
auth0.webAuth
.authorize({
scope: 'openid profile email'
})
.then(credentials => {
console.log('Logged in!');
})
.catch(error => {
console.log(error);
});
flutter_auth0 を使用する場合:
import 'package:auth0_flutter/auth0_flutter.dart';
final auth0 = Auth0(
'login.example.com',
'YOUR_CLIENT_ID'
);
// ログイン
try {
final credentials = await auth0.webAuthentication().login();
print('Logged in successfully');
} catch (e) {
print('Login failed: $e');
}
Management SDK は、Auth0 の Management API と連携するために使用します。カスタムドメインを使用している場合は、auth0-custom-domain ヘッダーを含めるか、デフォルトドメインを使用する必要がある場合があります。
import { ManagementClient, CustomDomainHeader } from "auth0";
// グローバルカスタムドメイン(ホワイトリストに登録されたすべてのリクエストに送信)
const management = new ManagementClient({
domain: 'tenant.auth0.com',
clientId: 'YOUR_M2M_CLIENT_ID',
clientSecret: 'YOUR_M2M_CLIENT_SECRET',
withCustomDomainHeader: 'login.example.com',
});
// ユーザー一覧取得(ホワイトリストに登録されたエンドポイント - ヘッダーは自動送信)
const users = await management.users.getAll();
// リクエスト単位の上書き(グローバル設定より優先)
const reqOptions = {
...CustomDomainHeader("specific-user-request.exampleco.com"),
};
const response = await management.users.getAll({}, reqOptions);
from auth0.management import ManagementClient, CustomDomainHeader
# グローバルカスタムドメイン(ホワイトリストに登録されたすべてのリクエストに送信)
client = ManagementClient(
domain='tenant.auth0.com',
client_id='YOUR_M2M_CLIENT_ID',
client_secret='YOUR_M2M_CLIENT_SECRET',
custom_domain='login.example.com',
)
# ユーザー一覧取得(ホワイトリスト登録済みエンドポイント - ヘッダーは自動送信)
users = client.users.list()
# リクエストごとの上書き(グローバル設定より優先)
client.users.create(
connection='Username-Password-Authentication',
email='user@example.com',
password='SecurePass123!',
request_options=CustomDomainHeader('login.brand2.com'),
)
import (
"context"
management "github.com/auth0/go-auth0/v2/management/client"
"github.com/auth0/go-auth0/v2/management/option"
)
// クライアントレベル: ホワイトリスト登録済みエンドポイントにカスタムドメインヘッダーを自動適用
mgmt, err := management.New(
"{yourDomain}",
option.WithClientCredentials("{yourClientId}", "{yourClientSecret}"),
option.WithCustomDomainHeader("login.example.com"),
)
if err != nil {
// エラー処理
}
// ユーザー一覧取得(ホワイトリスト登録済みエンドポイント - ヘッダーは自動送信)
userList, err := mgmt.Users.List(context.Background(), nil)
// リクエストごとの上書き(クライアントレベルより優先)
userList, err := mgmt.Users.List(
context.Background(),
nil,
option.WithCustomDomainHeader("specific-request.exampleco.com"),
)
カスタムドメインを使用する場合は、発行元としてカスタムドメインを許可するようにトークン検証を更新します。
express-jwt または jose を使用する場合:
const { expressjwt } = require('express-jwt');
const { expressJwtSecret } = require('jwks-rsa');
app.use(
expressjwt({
secret: expressJwtSecret({
cache: true,
rateLimit: true,
jwksUri: 'https://login.example.com/.well-known/jwks.json' // カスタムドメイン
}),
audience: 'YOUR_API_IDENTIFIER',
issuer: 'https://login.example.com/', // 発行元としてのカスタムドメイン
algorithms: ['RS256']
})
);
複数のカスタムドメインを使用する場合:
const validIssuers = [
'https://login.brand1.com/',
'https://login.brand2.com/',
'https://login.example.com/'
];
app.use(
expressjwt({
secret: expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: (req) => {
// トークンから発行元を抽出してJWKS URIを決定する
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
return `${payload.iss}.well-known/jwks.json`;
}
return 'https://login.example.com/.well-known/jwks.json';
}
}),
audience: 'YOUR_API_IDENTIFIER',
issuer: validIssuers, // 複数の発行元を許可する
algorithms: ['RS256']
})
);
python-jose を使用する場合は、以下のとおりです。
from jose import jwt
from functools import wraps
from flask import request, jsonify
def get_token_auth_header():
auth = request.headers.get('Authorization', None)
if not auth:
raise Exception('Authorization header is expected')
parts = auth.split()
if parts[0].lower() != 'bearer':
raise Exception('Authorization header must start with Bearer')
elif len(parts) == 1:
raise Exception('Token not found')
elif len(parts) > 2:
raise Exception('Authorization header must be Bearer token')
return parts[1]
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header()
# 複数のカスタムドメインをサポート
valid_issuers = [
'https://login.brand1.com/',
'https://login.brand2.com/',
'https://login.example.com/'
]
try:
# カスタムドメインからJWKSを取得
unverified = jwt.get_unverified_header(token)
issuer = jwt.get_unverified_claims(token)['iss']
if issuer not in valid_issuers:
raise Exception('Invalid issuer')
jwks_uri = f"{issuer}.well-known/jwks.json"
jwks = requests.get(jwks_uri).json()
payload = jwt.decode(
token,
jwks,
algorithms=['RS256'],
audience='YOUR_API_IDENTIFIER',
issuer=valid_issuers
)
except Exception as e:
return jsonify({'error': str(e)}), 401
return f(*args, **kwargs)
return decorated
Spring Security を使用する場合:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${auth0.audience}")
private String audience;
@Value("${auth0.custom-domain}")
private String customDomain;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/api/public").permitAll()
.mvcMatchers("/api/private").authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(jwtDecoder());
}
@Bean
JwtDecoder jwtDecoder() {
String issuerUri = "https://" + customDomain + "/";
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
// オーディエンスを検証
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
環境ごとにカスタムドメインを管理するには、環境変数を使用します。
# 開発環境
AUTH0_DOMAIN=dev.example.com
AUTH0_CLIENT_ID=dev_client_id
AUTH0_CLIENT_SECRET=dev_client_secret
# ステージング環境
# AUTH0_DOMAIN=staging.example.com
# AUTH0_CLIENT_ID=staging_client_id
# AUTH0_CLIENT_SECRET=staging_client_secret
# 本番環境
# AUTH0_DOMAIN=login.example.com
# AUTH0_CLIENT_ID=prod_client_id
# AUTH0_CLIENT_SECRET=prod_client_secret
require('dotenv').config();
const auth0Config = {
domain: process.env.AUTH0_DOMAIN,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET
};
| 問題 | 原因 | 解決策 |
|---|
| 発行元 が無効というエラー | トークン検証では正規ドメインを想定していますが、実際にはカスタムドメインを受け取っています | 発行元 としてカスタムドメインを許可するようにトークン検証を更新します |
| JWKS の取得に失敗する | JWKS URI が正規ドメインを指しています | JWKS URI がカスタムドメインを使用するように更新します: https://custom-domain/.well-known/jwks.json |
| リダイレクト URI の不一致 | コールバック URL が設定されているリダイレクト URI と一致していません | アプリケーション設定にカスタムドメインのコールバック URL を追加します |
| クロスオリジン エラー (CORS) | 許可されたオリジンにカスタムドメインが含まれていません | アプリケーション設定の Allowed Web Origins にカスタムドメインを追加します |
| Lock の読み込みに失敗する | configurationBaseUrl が設定されていません | リージョン別 CDN URL を指定して configurationBaseUrl パラメーターを追加します |
- 環境変数を使用する: カスタムドメインは環境ごとの設定ファイルに保存します
- 複数の発行元を検証する: 複数のカスタムドメインを使用する場合は、それらすべてを有効な発行元として受け入れるようにトークン検証を設定します
- コールバック URL を更新する: すべてのカスタムドメインが、アプリケーション設定の Allowed Callback URLs に追加されていることを確認します
- 十分にテストする: 本番環境に移行する前に、各カスタムドメイン経由の認証をテストします
- トークンの発行元を監視する: トークン内の
iss クレームを記録して監視し、正しいカスタムドメインが使用されていることを確認します
- ドメインマッピングを文書化する: どのアプリケーションがどのカスタムドメインを使用しているかを、明確に文書化して維持します
- 障害を適切に処理する: 認証の失敗に対する適切なエラーハンドリングを実装します
- JWKS をキャッシュする: パフォーマンスを向上させ、リクエスト数を減らすために JWKS データをキャッシュします