|
44 | 44 | - [Using MRRT with Hooks](#using-mrrt-with-hooks) |
45 | 45 | - [Using MRRT with Auth0 Class](#using-mrrt-with-auth0-class) |
46 | 46 | - [Web Platform Configuration](#web-platform-configuration) |
| 47 | +- [Custom Token Exchange (RFC 8693)](#custom-token-exchange-rfc-8693) |
| 48 | + - [Using Custom Token Exchange with Hooks](#using-custom-token-exchange-with-hooks) |
| 49 | + - [Using Custom Token Exchange with Auth0 Class](#using-custom-token-exchange-with-auth0-class) |
| 50 | + - [With Organization Context](#with-organization-context) |
| 51 | + - [Subject Token Type Requirements](#subject-token-type-requirements) |
47 | 52 | - [Native to Web SSO (Early Access)](#native-to-web-sso-early-access) |
48 | 53 | - [Overview](#native-to-web-sso-overview) |
49 | 54 | - [Prerequisites](#native-to-web-sso-prerequisites) |
@@ -758,6 +763,250 @@ function App() { |
758 | 763 | } |
759 | 764 | ``` |
760 | 765 |
|
| 766 | +## Custom Token Exchange (RFC 8693) |
| 767 | +
|
| 768 | +Custom Token Exchange allows you to exchange external identity provider tokens for Auth0 tokens using the [RFC 8693 OAuth 2.0 Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) specification. This enables scenarios where users authenticate with an external system and that token needs to be exchanged for Auth0 tokens. |
| 769 | +
|
| 770 | +> ⚠️ **Important**: The external token must be validated in Auth0 Actions using cryptographic verification. See the [Auth0 Custom Token Exchange documentation](https://auth0.com/docs/authenticate/custom-token-exchange) for setup instructions. |
| 771 | +
|
| 772 | +### Using Custom Token Exchange with Hooks |
| 773 | +
|
| 774 | +```typescript |
| 775 | +import React from 'react'; |
| 776 | +import { Button, Alert } from 'react-native'; |
| 777 | +import { |
| 778 | + useAuth0, |
| 779 | + AuthenticationException, |
| 780 | + AuthenticationErrorCodes, |
| 781 | +} from 'react-native-auth0'; |
| 782 | + |
| 783 | +function TokenExchangeScreen() { |
| 784 | + const { customTokenExchange, user, error } = useAuth0(); |
| 785 | + |
| 786 | + const handleExchange = async () => { |
| 787 | + try { |
| 788 | + // Exchange an external token for Auth0 tokens |
| 789 | + const credentials = await customTokenExchange({ |
| 790 | + subjectToken: 'token-from-external-provider', |
| 791 | + subjectTokenType: 'urn:acme:legacy-system-token', |
| 792 | + scope: 'openid profile email', |
| 793 | + audience: 'https://api.example.com', |
| 794 | + }); |
| 795 | + |
| 796 | + Alert.alert('Success', `Logged in as ${user?.name}`); |
| 797 | + } catch (e) { |
| 798 | + if (e instanceof AuthenticationException) { |
| 799 | + switch (e.type) { |
| 800 | + case AuthenticationErrorCodes.INVALID_SUBJECT_TOKEN: |
| 801 | + Alert.alert('Error', 'The external token is invalid or expired'); |
| 802 | + break; |
| 803 | + case AuthenticationErrorCodes.UNSUPPORTED_TOKEN_TYPE: |
| 804 | + Alert.alert('Error', 'The token type is not supported'); |
| 805 | + break; |
| 806 | + case AuthenticationErrorCodes.TOKEN_EXCHANGE_NOT_CONFIGURED: |
| 807 | + Alert.alert( |
| 808 | + 'Error', |
| 809 | + 'Custom Token Exchange is not configured for this tenant' |
| 810 | + ); |
| 811 | + break; |
| 812 | + case AuthenticationErrorCodes.TOKEN_VALIDATION_FAILED: |
| 813 | + Alert.alert('Error', 'Token validation failed in Auth0 Action'); |
| 814 | + break; |
| 815 | + case AuthenticationErrorCodes.NETWORK_ERROR: |
| 816 | + Alert.alert('Error', 'Network error. Please check your connection.'); |
| 817 | + break; |
| 818 | + default: |
| 819 | + Alert.alert('Error', e.message); |
| 820 | + } |
| 821 | + } else { |
| 822 | + console.error('Token exchange failed:', e); |
| 823 | + } |
| 824 | + } |
| 825 | + }; |
| 826 | +
|
| 827 | + return <Button onPress={handleExchange} title="Exchange Token" />; |
| 828 | +} |
| 829 | +``` |
| 830 | +
|
| 831 | +### Using Custom Token Exchange with Auth0 Class |
| 832 | +
|
| 833 | +```typescript |
| 834 | +import Auth0, { |
| 835 | + AuthenticationException, |
| 836 | + AuthenticationErrorCodes, |
| 837 | +} from 'react-native-auth0'; |
| 838 | +
|
| 839 | +const auth0 = new Auth0({ |
| 840 | + domain: 'YOUR_AUTH0_DOMAIN', |
| 841 | + clientId: 'YOUR_CLIENT_ID', |
| 842 | +}); |
| 843 | +
|
| 844 | +async function exchangeExternalToken(externalToken: string) { |
| 845 | + try { |
| 846 | + const credentials = await auth0.customTokenExchange({ |
| 847 | + subjectToken: externalToken, |
| 848 | + subjectTokenType: 'urn:acme:legacy-system-token', |
| 849 | + audience: 'https://api.example.com', |
| 850 | + scope: 'openid profile email', |
| 851 | + }); |
| 852 | +
|
| 853 | + console.log('Exchange successful:', credentials); |
| 854 | + return credentials; |
| 855 | + } catch (error) { |
| 856 | + if (error instanceof AuthenticationException) { |
| 857 | + // Access the underlying error details |
| 858 | + console.error('Error type:', error.type); |
| 859 | + console.error('Error message:', error.message); |
| 860 | + console.error('Underlying error code:', error.underlyingError.code); |
| 861 | +
|
| 862 | + // Handle specific error types |
| 863 | + if (error.type === AuthenticationErrorCodes.INVALID_SUBJECT_TOKEN) { |
| 864 | + // Token is invalid or expired - prompt user to re-authenticate |
| 865 | + throw new Error('Please authenticate again with the external provider'); |
| 866 | + } |
| 867 | + } |
| 868 | + throw error; |
| 869 | + } |
| 870 | +} |
| 871 | +``` |
| 872 | +
|
| 873 | +### With Organization Context |
| 874 | +
|
| 875 | +Exchange tokens within a specific organization context: |
| 876 | +
|
| 877 | +```typescript |
| 878 | +const credentials = await customTokenExchange({ |
| 879 | + subjectToken: 'external-provider-token', |
| 880 | + subjectTokenType: 'urn:acme:legacy-system-token', |
| 881 | + organization: 'org_123', // or organization name |
| 882 | + scope: 'openid profile email', |
| 883 | +}); |
| 884 | +``` |
| 885 | +
|
| 886 | +### Subject Token Type Requirements |
| 887 | +
|
| 888 | +The `subjectTokenType` parameter must be a **unique profile token type URI** starting with `https://` or `urn:`. |
| 889 | +
|
| 890 | +#### Valid Token Type Patterns |
| 891 | +
|
| 892 | +You control the token type namespace. Use one of these patterns: |
| 893 | +
|
| 894 | +**URN Format (Recommended):** |
| 895 | +
|
| 896 | +- `urn:yourcompany:token-type` - Company-specific token type |
| 897 | +- `urn:acme:legacy-system-token` - Legacy system tokens |
| 898 | +- `urn:example:external-idp` - External IdP tokens |
| 899 | +
|
| 900 | +**HTTPS URL Format:** |
| 901 | +
|
| 902 | +- `https://yourcompany.com/tokens/legacy` - Using your organization's domain |
| 903 | +- `https://example.com/custom-token` - Custom token identifier |
| 904 | +
|
| 905 | +#### Reserved Namespaces (Forbidden) |
| 906 | +
|
| 907 | +The following namespaces are **reserved** and you **CANNOT use them**: |
| 908 | +
|
| 909 | +- ❌ `http://auth0.com/*` |
| 910 | +- ❌ `https://auth0.com/*` |
| 911 | +- ❌ `http://okta.com/*` |
| 912 | +- ❌ `https://okta.com/*` |
| 913 | +- ❌ `urn:ietf:*` |
| 914 | +- ❌ `urn:auth0:*` |
| 915 | +- ❌ `urn:okta:*` |
| 916 | +
|
| 917 | +#### Common Use Cases |
| 918 | +
|
| 919 | +1. **Seamless Migration from Legacy IdP**: Exchange legacy refresh tokens |
| 920 | +
|
| 921 | + ```typescript |
| 922 | + await customTokenExchange({ |
| 923 | + subjectToken: legacyRefreshToken, |
| 924 | + subjectTokenType: 'urn:acme:legacy-system-token', |
| 925 | + scope: 'openid profile email offline_access', |
| 926 | + }); |
| 927 | + ``` |
| 928 | +
|
| 929 | +2. **External Authentication Provider**: Exchange tokens from partner IdP |
| 930 | +
|
| 931 | + ```typescript |
| 932 | + await customTokenExchange({ |
| 933 | + subjectToken: externalProviderToken, |
| 934 | + subjectTokenType: 'urn:partner:auth-token', |
| 935 | + scope: 'openid profile email', |
| 936 | + }); |
| 937 | + ``` |
| 938 | +
|
| 939 | +3. **Custom JWT Tokens**: Exchange JWTs from your own system |
| 940 | + ```typescript |
| 941 | + await customTokenExchange({ |
| 942 | + subjectToken: customJwt, |
| 943 | + subjectTokenType: 'urn:yourcompany:jwt-token', |
| 944 | + audience: 'https://api.example.com', |
| 945 | + }); |
| 946 | + ``` |
| 947 | + |
| 948 | +### Error Codes Reference |
| 949 | + |
| 950 | +Custom Token Exchange throws `AuthError` with specific error codes for different failure scenarios. Use the `code` property for programmatic error handling: |
| 951 | + |
| 952 | +```typescript |
| 953 | +try { |
| 954 | + await auth0.customTokenExchange({...}); |
| 955 | +} catch (error) { |
| 956 | + console.error('Error code:', error.code); |
| 957 | + console.error('Error message:', error.message); |
| 958 | +
|
| 959 | + // Handle specific errors |
| 960 | + if (error.code === 'invalid_grant') { |
| 961 | + // Handle invalid token |
| 962 | + } |
| 963 | +} |
| 964 | +``` |
| 965 | + |
| 966 | +| Error Code | Description | |
| 967 | +| ----------------------------------- | ---------------------------------------------------------- | |
| 968 | +| `custom_token_exchange_failed` | General token exchange failure | |
| 969 | +| `invalid_grant` | The external token is invalid, malformed, or expired | |
| 970 | +| `invalid_request` | The request is missing required parameters or is malformed | |
| 971 | +| `unsupported_token_type` | The token type is not supported or recognized | |
| 972 | +| `unauthorized_client` | Custom Token Exchange is not enabled for this client | |
| 973 | +| `invalid_target` | The requested audience is invalid or not allowed | |
| 974 | +| `invalid_scope` | The requested scope is invalid or not allowed | |
| 975 | +| `access_denied` | Token exchange was denied by the authorization server | |
| 976 | +| `server_error` | The authorization server encountered an internal error | |
| 977 | +| `temporarily_unavailable` | The server is temporarily unable to handle the request | |
| 978 | +| `network_error` | Network connectivity issue occurred | |
| 979 | +| `a0.token_exchange_failed` | Auth0-specific token exchange failure | |
| 980 | +| `a0.action_failed` | The token validation in Auth0 Action failed | |
| 981 | +| `a0.invalid_subject_token` | Subject token validation failed | |
| 982 | +| `a0.unsupported_subject_token_type` | Subject token type is not supported | |
| 983 | + |
| 984 | +These error codes follow: |
| 985 | + |
| 986 | +- **RFC 8693 standard**: `invalid_grant`, `invalid_request`, `unsupported_token_type`, `access_denied`, etc. |
| 987 | +- **Auth0-specific codes**: `a0.token_exchange_failed`, `a0.action_failed`, etc. |
| 988 | + |
| 989 | +### Auth0 Actions Validation |
| 990 | + |
| 991 | +Custom Token Exchange requires validation of the subject token in Auth0 Actions. The Action must: |
| 992 | + |
| 993 | +1. **Validate the subject token** cryptographically (verify signature, expiration, issuer, etc.) |
| 994 | +2. **Apply authorization policy** to determine if the exchange is allowed |
| 995 | +3. **Set the user** using one of the `api.authentication.setUser*()` methods |
| 996 | + |
| 997 | +For detailed examples of validating different token types in Actions, see: |
| 998 | + |
| 999 | +- [Auth0 Custom Token Exchange Documentation](https://auth0.com/docs/authenticate/custom-token-exchange) |
| 1000 | +- [Example Use Cases](https://auth0.com/docs/authenticate/custom-token-exchange/cte-example-use-cases) |
| 1001 | + |
| 1002 | +**Security Best Practices:** |
| 1003 | + |
| 1004 | +- Use asymmetric algorithms (RS256, ES256) whenever possible |
| 1005 | +- Store secrets in Actions Secrets, never hardcode them |
| 1006 | +- Cache JWKS keys using `api.cache.set()` to improve performance |
| 1007 | +- Validate token expiration, issuer, and audience claims |
| 1008 | +- Implement rate limiting for failed validations using `api.access.rejectInvalidSubjectToken()` |
| 1009 | + |
761 | 1010 | ## Native to Web SSO (Early Access) |
762 | 1011 |
|
763 | 1012 | > ⚠️ **Early Access Feature**: Native to Web SSO is currently available in Early Access. To use this feature, you must have an Enterprise plan. For more information, see [Product Release Stages](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages). |
|
0 commit comments