> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Aprende a configurar los SDK de Auth0 para que funcionen con varios dominios personalizados.

# Configuración de SDK para varios dominios personalizados

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****ENMASCARADO*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

Al usar [varios dominios personalizados](/es/docs/customize/custom-domains/multiple-custom-domains), debe configurar sus SDK de Auth0 para usar el dominio personalizado adecuado para la autenticación. Esta guía abarca la configuración de los SDK para distintas plataformas y escenarios.

<div id="key-concepts">
  ## Conceptos clave
</div>

<div id="domain-parameter">
  ### Parámetro de dominio
</div>

Todos los SDK de Auth0 requieren un parámetro `domain` que especifica qué dominio de Auth0 se usará para la autenticación. Si usa dominios personalizados, establezca este parámetro en su dominio personalizado en lugar del dominio canónico de Auth0.

**Sin dominio personalizado:**

```
domain: 'tenant.auth0.com'
```

**Con un dominio personalizado:**

```
domain: 'login.example.com'
```

<div id="token-issuer">
  ### Emisor del token
</div>

Al usar un dominio personalizado, los tokens tendrán la reclamación `iss` (emisor) configurada con su dominio personalizado:

```json theme={null}
{
  "iss": "https://login.example.com/",
  "sub": "auth0|123456",
  "aud": "your-client-id"
}
```

Debe configurar la validación de su token para aceptar su dominio personalizado como emisor válido.

<div id="authentication-sdks">
  ## SDK de autenticación
</div>

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Al usar MCD, el cliente es responsable de proporcionar y validar todos los dominios personalizados. Al configurar los SDK para resolver los dominios personalizados del Tenant mediante las funciones de resolución de dominios, usted es responsable de garantizar que todos los dominios resueltos sean de confianza. Una configuración incorrecta del resolvedor de dominios puede permitir la omisión de la autenticación en la relying party o exponer la aplicación a falsificación de solicitudes del lado del servidor. No configurar correctamente sus dominios y servidores proxy puede generar vulnerabilidades críticas de seguridad por las que Okta no asume ninguna responsabilidad.
</Callout>

<div id="auth0-spa-sdk-javascript">
  ### SDK SPA de Auth0 (JavaScript)
</div>

Para aplicaciones de página única que usan el [SDK SPA de Auth0](https://github.com/auth0/auth0-spa-js):

<AuthCodeGroup>
  ```javascript Configuración estática theme={null}
  import { createAuth0Client } from '@auth0/auth0-spa-js';

  const auth0 = await createAuth0Client({
    domain: 'login.example.com',  // Tu dominio personalizado
    clientId: 'YOUR_CLIENT_ID',
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });
  ```

  ```javascript Configuración dinámica theme={null}
  import { createAuth0Client } from '@auth0/auth0-spa-js';

  // Determina el dominio según el contexto de la aplicación
  function getAuth0Domain() {
    const hostname = window.location.hostname;

    // Asocia los dominios de la aplicación con dominios personalizados de Auth0
    if (hostname.includes('app1.example.com')) {
      return 'login.brand1.com';
    } else if (hostname.includes('app2.example.com')) {
      return 'login.brand2.com';
    }

    // Dominio personalizado predeterminado
    return 'login.example.com';
  }

  const auth0 = await createAuth0Client({
    domain: getAuth0Domain(),
    clientId: 'YOUR_CLIENT_ID',
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });
  ```

  ```javascript Variables de entorno theme={null}
  import { createAuth0Client } from '@auth0/auth0-spa-js';

  const auth0 = await createAuth0Client({
    domain: process.env.REACT_APP_AUTH0_DOMAIN,  // Del archivo .env
    clientId: process.env.REACT_APP_AUTH0_CLIENT_ID,
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  });
  ```
</AuthCodeGroup>

<div id="nextjs">
  ### Next.js
</div>

Para aplicaciones de Next.js que usan el [SDK de Auth0 para Next.js](https://github.com/auth0/nextjs-auth0) (v4+):

<AuthCodeGroup>
  ```typescript Configuración estática theme={null}
  import { Auth0Client } from '@auth0/nextjs-auth0/server';

  const auth0 = new Auth0Client({
    domain: 'login.example.com',        // Tu dominio personalizado
    clientId: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    appBaseUrl: process.env.APP_BASE_URL,
    secret: process.env.AUTH0_SECRET
  });
  ```

  ```typescript Configuración dinámica (multimarca) theme={null}
  import { Auth0Client } from '@auth0/nextjs-auth0/server';

  const auth0 = new Auth0Client({
    domain: ({ headers }) => {
      // Asigna los hosts de la aplicación a dominios personalizados de Auth0
      const host = headers.get('host') || '';

      if (host.includes('brand1.example.com')) {
        return 'login.brand1.com';
      } else if (host.includes('brand2.example.com')) {
        return 'login.brand2.com';
      }

      return 'login.example.com';  // Dominio predeterminado
    },
    clientId: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    appBaseUrl: process.env.APP_BASE_URL,
    secret: process.env.AUTH0_SECRET
  });
  ```

  ```typescript Configuración dinámica (SaaS B2B) theme={null}
  import { Auth0Client } from '@auth0/nextjs-auth0/server';

  // Asigna identificadores de Tenant a dominios personalizados (desde una base de datos, caché, etc.)
  async function resolveTenantDomain(host: string): Promise<string> {
    // Extrae el identificador del Tenant del subdominio
    const subdomain = host.split('.')[0];

    // Busca el dominio personalizado de este Tenant (por ejemplo, en una base de datos)
    const tenantConfig = await getTenantConfig(subdomain);

    return tenantConfig?.customDomain || 'login.example.com';
  }

  const auth0 = new Auth0Client({
    domain: async ({ headers }) => {
      const host = headers.get('host') || '';
      return resolveTenantDomain(host);
    },
    clientId: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    appBaseUrl: process.env.APP_BASE_URL,
    secret: process.env.AUTH0_SECRET
  });
  ```

  ```bash Variables de entorno theme={null}
  # .env.local

  # Configuración de Auth0 (un solo Tenant con varios dominios personalizados)
  AUTH0_DOMAIN=login.example.com           # Dominio personalizado predeterminado o de respaldo
  AUTH0_CLIENT_ID=your_client_id
  AUTH0_CLIENT_SECRET=your_client_secret
  APP_BASE_URL=http://localhost:3000
  AUTH0_SECRET=your-long-random-secret
  ```
</AuthCodeGroup>

**Conceptos clave de MCD con Next.js:**

* **Un solo Tenant de Auth0, varios dominios**: Todos los dominios personalizados comparten el mismo `clientId` y `clientSecret`, ya que pertenecen al mismo Tenant de Auth0.
* **Función DomainResolver**: El parámetro `domain` acepta una función `(config: { headers: Headers; url?: URL }) => Promise<string> | string`. Esto permite resolver el dominio dinámicamente para cada solicitud en función de los encabezados de la solicitud entrante.
* **Almacenamiento en caché de instancias**: El SDK almacena automáticamente en caché las instancias de `Auth0Client` por dominio mediante una caché LRU acotada (máximo 100 entradas) para mejorar el rendimiento.
* **Aislamiento de sesiones**: Las sesiones creadas a través de un dominio personalizado quedan aisladas en ese dominio y no pueden usarse indistintamente con sesiones de otro dominio.
* **Parámetro URL**: El parámetro `url` del resolvedor es `undefined` en Server Components y Server Actions; solo está disponible en middleware o rutas de API.
* **Ajuste de la caché de descubrimiento**: Configura el almacenamiento en caché de los metadatos de OIDC con la opción `discoveryCache`:
  ```typescript theme={null}
  const auth0 = new Auth0Client({
    // ... otra configuración
    discoveryCache: {
      ttl: 600,      // Caché durante 10 minutos (predeterminado)
      maxEntries: 100  // Número máximo de emisores almacenados en caché (predeterminado: 100, expulsión LRU)
    }
  });
  ```

<div id="auth0-react-sdk">
  ### SDK de React de Auth0
</div>

Para aplicaciones React que usan el [SDK de React de Auth0](https://github.com/auth0/auth0-react):

```jsx theme={null}
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>
  );
}
```

Para escenarios con varios dominios:

```jsx theme={null}
import { Auth0Provider } from '@auth0/auth0-react';

function App() {
  // Determinar el dominio personalizado según el entorno o contexto
  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>
  );
}
```

<div id="auth0js">
  ### Auth0.js
</div>

Para las aplicaciones que usan [Auth0.js](https://github.com/auth0/auth0.js):

```javascript theme={null}
const webAuth = new auth0.WebAuth({
  domain: 'login.example.com',
  clientID: 'YOUR_CLIENT_ID',
  redirectUri: window.location.origin + '/callback',
  responseType: 'code',
  scope: 'openid profile email'
});

// Iniciar sesión
webAuth.authorize();
```

<div id="nodejs-express">
  ### Node.js (Express)
</div>

Para aplicaciones Node.js que usan [express-openid-connect](https://github.com/auth0/express-openid-connect):

```javascript theme={null}
const { auth } = require('express-openid-connect');

app.use(
  auth({
    authRequired: false,
    auth0Logout: true,
    issuerBaseURL: 'https://login.example.com',  // Tu dominio personalizado
    baseURL: 'http://localhost:3000',
    clientID: 'YOUR_CLIENT_ID',
    secret: 'YOUR_CLIENT_SECRET'
  })
);
```

Para escenarios multiinquilino:

```javascript theme={null}
const { auth } = require('express-openid-connect');

// Middleware para determinar el dominio personalizado por solicitud
app.use((req, res, next) => {
  // Extraer el identificador de Tenant del subdominio, ruta o encabezado
  const tenant = req.subdomains[0] || 'default';

  // Asignar el Tenant al dominio personalizado
  const domainMap = {
    'customer1': 'login.customer1.com',
    'customer2': 'login.customer2.com',
    'default': 'login.example.com'
  };

  req.auth0Domain = domainMap[tenant] || domainMap.default;
  next();
});

// Configuración de autenticación dinámica
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);
});
```

<div id="mobile-sdks">
  ### SDK para móviles
</div>

<div id="ios-swift">
  #### iOS (Swift)
</div>

Con [Auth0.swift](https://github.com/auth0/Auth0.swift):

```swift theme={null}
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)")
        }
    }
```

Para seleccionar el dominio dinámicamente:

```swift theme={null}
import Auth0

class AuthService {
    private let clientId = "YOUR_CLIENT_ID"

    func getAuth0Domain() -> String {
        // Determinar el dominio según la configuración de la aplicación
        if let savedDomain = UserDefaults.standard.string(forKey: "auth0Domain") {
            return savedDomain
        }
        return "login.example.com" // Predeterminado
    }

    func login(completion: @escaping (Result<Credentials, Error>) -> Void) {
        Auth0
            .webAuth(clientId: clientId, domain: getAuth0Domain())
            .scope("openid profile email")
            .start { result in
                completion(result)
            }
    }
}
```

<div id="android-kotlin">
  #### Android (Kotlin)
</div>

Uso de [Auth0.Android](https://github.com/auth0/Auth0.Android):

```kotlin theme={null}
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"  // Tu dominio personalizado
)

WebAuthProvider.login(account)
    .withScheme("demo")
    .withScope("openid profile email")
    .start(this, object : Callback<Credentials, AuthenticationException> {
        override fun onSuccess(credentials: Credentials) {
            // Manejar el éxito
        }

        override fun onFailure(exception: AuthenticationException) {
            // Manejar el error
        }
    })
```

Para admitir varios dominios:

```kotlin theme={null}
class AuthManager(private val context: Context) {
    private val clientId = "YOUR_CLIENT_ID"

    private fun getAuth0Domain(): String {
        // Recuperar de las preferencias compartidas o la configuración de la aplicación
        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)
    }
}
```

<div id="react-native">
  #### React Native
</div>

Con [react-native-auth0](https://github.com/auth0/react-native-auth0):

```javascript theme={null}
import Auth0 from 'react-native-auth0';

const auth0 = new Auth0({
  domain: 'login.example.com',
  clientId: 'YOUR_CLIENT_ID'
});

// Inicio de sesión
auth0.webAuth
  .authorize({
    scope: 'openid profile email'
  })
  .then(credentials => {
    console.log('Logged in!');
  })
  .catch(error => {
    console.log(error);
  });
```

<div id="flutter">
  ### Flutter
</div>

Con [flutter\_auth0](https://github.com/auth0/auth0-flutter):

```dart theme={null}
import 'package:auth0_flutter/auth0_flutter.dart';

final auth0 = Auth0(
  'login.example.com',
  'YOUR_CLIENT_ID'
);

// Inicio de sesión
try {
  final credentials = await auth0.webAuthentication().login();
  print('Logged in successfully');
} catch (e) {
  print('Login failed: $e');
}
```

<div id="management-sdks">
  ## SDK de administración
</div>

Los SDK de administración se utilizan para interactuar con la Management API de Auth0. Al usar dominios personalizados, es posible que deba incluir el encabezado `auth0-custom-domain` o usar el [dominio predeterminado](/es/docs/customize/custom-domains/multiple-custom-domains/default-domain).

<div id="nodejs-management-sdk">
  ### SDK de administración de Node.js
</div>

```javascript theme={null}
import { ManagementClient, CustomDomainHeader } from "auth0";

// Dominio personalizado global (enviado en todas las solicitudes en lista blanca)
const management = new ManagementClient({
  domain: 'tenant.auth0.com',
  clientId: 'YOUR_M2M_CLIENT_ID',
  clientSecret: 'YOUR_M2M_CLIENT_SECRET',
  withCustomDomainHeader: 'login.example.com', 
});

// Listar usuarios (endpoint en lista blanca - el encabezado se envía automáticamente)
const users = await management.users.getAll();

// Anulación por solicitud (tiene precedencia sobre la configuración global)
const reqOptions = {
  ...CustomDomainHeader("specific-user-request.exampleco.com"),
};
const response = await management.users.getAll({}, reqOptions);
```

<div id="python-management-sdk">
  ### SDK de administración para Python
</div>

```python theme={null}
from auth0.management import ManagementClient, CustomDomainHeader

# Dominio personalizado global (enviado en todas las solicitudes de la lista blanca)
client = ManagementClient(
    domain='tenant.auth0.com',
    client_id='YOUR_M2M_CLIENT_ID',
    client_secret='YOUR_M2M_CLIENT_SECRET',
    custom_domain='login.example.com',
)

# Listar usuarios (endpoint en lista blanca - el encabezado se envía automáticamente)
users = client.users.list()

# Anulación por solicitud (tiene precedencia sobre el global)
client.users.create(
    connection='Username-Password-Authentication',
    email='user@example.com',
    password='SecurePass123!',
    request_options=CustomDomainHeader('login.brand2.com'),
)
```

<div id="go-management-sdk">
  ### SDK de administración para Go
</div>

```go theme={null}
import (
    "context"
    management "github.com/auth0/go-auth0/v2/management/client"
    "github.com/auth0/go-auth0/v2/management/option"
)

// Nivel de cliente: aplica automáticamente el encabezado de dominio personalizado a los endpoints en lista blanca
mgmt, err := management.New(
    "{yourDomain}",
    option.WithClientCredentials("{yourClientId}", "{yourClientSecret}"),
    option.WithCustomDomainHeader("login.example.com"),
)
if err != nil {
    // Gestionar error
}

// Listar usuarios (endpoint en lista blanca - el encabezado se envía automáticamente)
userList, err := mgmt.Users.List(context.Background(), nil)

// Anulación por solicitud (tiene prioridad sobre el nivel de cliente)
userList, err := mgmt.Users.List(
    context.Background(),
    nil,
    option.WithCustomDomainHeader("specific-request.exampleco.com"),
)
```

<div id="token-validation">
  ## Validación de tokens
</div>

Si usa dominios personalizados, actualice la validación de tokens para aceptar el dominio personalizado como emisor.

<div id="nodejs-express">
  ### Node.js (Express)
</div>

Con [express-jwt](https://github.com/auth0/express-jwt) o [jose](https://github.com/panva/jose):

```javascript theme={null}
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'  // Dominio personalizado
    }),
    audience: 'YOUR_API_IDENTIFIER',
    issuer: 'https://login.example.com/',  // Dominio personalizado como emisor
    algorithms: ['RS256']
  })
);
```

Para varios dominios personalizados:

```javascript theme={null}
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) => {
        // Extraer el emisor del token para determinar el URI de JWKS
        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,  // Aceptar múltiples emisores
    algorithms: ['RS256']
  })
);
```

<div id="python-flask">
  ### Python (Flask)
</div>

Con [python-jose](https://github.com/mpdavis/python-jose):

```python theme={null}
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()

        # Compatibilidad con múltiples dominios personalizados
        valid_issuers = [
            'https://login.brand1.com/',
            'https://login.brand2.com/',
            'https://login.example.com/'
        ]

        try:
            # Obtener JWKS del dominio personalizado
            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
```

<div id="java-spring-boot">
  ### Java (Spring Boot)
</div>

Uso de Spring Security:

```java theme={null}
@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);

        // Validar audiencia
        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }
}
```

<div id="environment-specific-configuration">
  ## Configuración específica del entorno
</div>

Use variables de entorno para administrar dominios personalizados entre entornos:

<div id="env-file-structure">
  ### Estructura del archivo .env
</div>

```bash theme={null}
# Desarrollo
AUTH0_DOMAIN=dev.example.com
AUTH0_CLIENT_ID=dev_client_id
AUTH0_CLIENT_SECRET=dev_client_secret

# Staging
# AUTH0_DOMAIN=staging.example.com
# AUTH0_CLIENT_ID=staging_client_id
# AUTH0_CLIENT_SECRET=staging_client_secret

# Producción
# AUTH0_DOMAIN=login.example.com
# AUTH0_CLIENT_ID=prod_client_id
# AUTH0_CLIENT_SECRET=prod_client_secret
```

<div id="loading-configuration">
  ### Carga de la configuración
</div>

```javascript theme={null}
require('dotenv').config();

const auth0Config = {
  domain: process.env.AUTH0_DOMAIN,
  clientId: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET
};
```

<div id="troubleshooting">
  ## Solución de problemas
</div>

<div id="common-issues">
  ### Problemas comunes
</div>

<table class="table">
  <thead>
    <tr>
      <th><strong>Problema</strong></th>
      <th><strong>Causa</strong></th>
      <th><strong>Solución</strong></th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Error de emisor no válido</td>
      <td>La validación del token espera el dominio canónico, pero recibe un dominio personalizado</td>
      <td>Actualiza la validación del token para que acepte el dominio personalizado como emisor</td>
    </tr>

    <tr>
      <td>Error al obtener el JWKS</td>
      <td>El URI del JWKS apunta al dominio canónico</td>
      <td>Actualiza el URI del JWKS para usar el dominio personalizado: `https://custom-domain/.well-known/jwks.json`</td>
    </tr>

    <tr>
      <td>Incompatibilidad del URI de redirección</td>
      <td>La URL de callback no coincide con los URI de redirección configurados</td>
      <td>Agrega la URL de callback del dominio personalizado a la configuración de la aplicación</td>
    </tr>

    <tr>
      <td>Errores de origen cruzado (CORS)</td>
      <td>El dominio personalizado no está entre los orígenes permitidos</td>
      <td>Agrega el dominio personalizado a Allowed Web Origins en la configuración de la aplicación</td>
    </tr>

    <tr>
      <td>Lock no se carga</td>
      <td>Falta `configurationBaseUrl`</td>
      <td>Agrega el parámetro `configurationBaseUrl` con la URL regional del CDN</td>
    </tr>
  </tbody>
</table>

<div id="best-practices">
  ## Prácticas recomendadas
</div>

1. **Usa variables de entorno**: Almacena los dominios personalizados en archivos de configuración específicos de cada entorno
2. **Valida varios emisores**: Si usas varios dominios personalizados, configura la validación de tokens para aceptar todos como emisores válidos
3. **Actualiza las URL de callback**: Asegúrate de agregar todos los dominios personalizados a Allowed Callback URLs en la configuración de la aplicación
4. **Haz pruebas exhaustivas**: Prueba la autenticación con cada dominio personalizado antes de pasar a producción
5. **Supervisa los emisores de tokens**: Registra y supervisa el claim `iss` en los tokens para garantizar que se use el dominio personalizado correcto
6. **Documenta las asignaciones de dominios**: Mantén una documentación clara sobre qué aplicaciones usan qué dominios personalizados
7. **Gestiona los fallos correctamente**: Implementa un manejo adecuado de errores para los fallos de autenticación
8. **Almacena en caché JWKS**: Almacena en caché los datos de JWKS para mejorar el rendimiento y reducir las solicitudes

<div id="learn-more">
  ## Más información
</div>

* [Varios dominios personalizados](/es/docs/customize/custom-domains/multiple-custom-domains)
* [Dominio personalizado predeterminado](/es/docs/customize/custom-domains/multiple-custom-domains/default-domain)
* [Configurar funciones para usar dominios personalizados](/es/docs/customize/custom-domains/configure-features-to-use-custom-domains)
* [SDKs de Auth0](/es/docs/troubleshoot/customer-support/product-support-matrix#auth0-sdks)
