メインコンテンツへスキップ
複数のカスタムドメインを使用する場合は、認証時に適切なカスタムドメインを使用するよう、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"
}
トークン検証で、カスタムドメインを有効な発行元として受け入れるように設定する必要があります。

認証 SDK

MCD を使用する場合、すべてのカスタムドメインの提供と検証はお客様の責任となります。SDK でドメインリゾルバー関数を使用してテナントのカスタムドメインを解決するよう設定する場合は、解決されるすべてのドメインが信頼できるものであることをお客様自身で必ず確認してください。ドメインリゾルバーの設定を誤ると、relying party で認証がバイパスされたり、アプリケーションがサーバーサイドリクエストフォージェリにさらされたりする可能性があります。ドメインおよびプロキシサーバーを適切に設定しないと、重大なセキュリティ脆弱性が生じるおそれがあり、これについて Okta は責任を負いません。

Auth0 SPA SDK (JavaScript)

Auth0 SPA SDK を使用するシングルページアプリケーションの場合は、次のように設定します。

Next.js

Auth0 Next.js SDK (v4+) を使用する Next.js アプリケーションの場合: Next.js で MCD を使用する際の主な概念:
  • 単一の Auth0 テナント、複数のドメイン: すべてのカスタムドメインは同じ Auth0 テナントに属するため、同じ clientIdclientSecret を共有します。
  • 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 による削除)
      }
    });
    

Auth0 React SDK

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

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();

Node.js (Express)

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);
});

モバイル向け SDK

iOS (Swift)

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)
            }
    }
}

Android (Kotlin)

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

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

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 SDKs

Management SDK は、Auth0 の Management API と連携するために使用します。カスタムドメインを使用している場合は、auth0-custom-domain ヘッダーを含めるか、デフォルトドメインを使用する必要がある場合があります。

Node.js Management SDK

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);

Python Management SDK

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'),
)

Go Management SDK

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"),
)

トークンの検証

カスタムドメインを使用する場合は、発行元としてカスタムドメインを許可するようにトークン検証を更新します。

Node.js (Express)

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 (Flask)

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

Java (Spring Boot)

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;
    }
}

環境ごとの設定

環境ごとにカスタムドメインを管理するには、環境変数を使用します。

.env ファイルの構成

# 開発環境
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 パラメーターを追加します

ベストプラクティス

  1. 環境変数を使用する: カスタムドメインは環境ごとの設定ファイルに保存します
  2. 複数の発行元を検証する: 複数のカスタムドメインを使用する場合は、それらすべてを有効な発行元として受け入れるようにトークン検証を設定します
  3. コールバック URL を更新する: すべてのカスタムドメインが、アプリケーション設定の Allowed Callback URLs に追加されていることを確認します
  4. 十分にテストする: 本番環境に移行する前に、各カスタムドメイン経由の認証をテストします
  5. トークンの発行元を監視する: トークン内の iss クレームを記録して監視し、正しいカスタムドメインが使用されていることを確認します
  6. ドメインマッピングを文書化する: どのアプリケーションがどのカスタムドメインを使用しているかを、明確に文書化して維持します
  7. 障害を適切に処理する: 認証の失敗に対する適切なエラーハンドリングを実装します
  8. JWKS をキャッシュする: パフォーマンスを向上させ、リクエスト数を減らすために JWKS データをキャッシュします

詳細