diff --git a/EXAMPLES.md b/EXAMPLES.md
index a3ef634e..9eb5ac68 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -1,5 +1,7 @@
# Examples using react-native-auth0
+- [SDK Configuration](#sdk-configuration)
+ - [Setting the Accept-Language Header](#setting-the-accept-language-header)
- [Authentication API](#authentication-api)
- [Login with Password Realm Grant](#login-with-password-realm-grant)
- [Get user information using user's access_token](#get-user-information-using-users-access_token)
@@ -21,6 +23,59 @@
- [iOS](#ios)
- [Expo](#expo)
+## SDK Configuration
+
+This section covers examples related to configuring the behavior of the Auth0 SDK itself.
+
+### Setting the Accept-Language Header
+
+You can configure the SDK to automatically send the `Accept-Language` HTTP header with all outgoing requests. This is useful if you want to signal your preferred language(s) to Auth0, which might influence the language used in error messages or other localized content returned by Auth0 APIs.
+
+
+Provide the `acceptLanguage` option during initialization. The value should be a string conforming to the standard `Accept-Language` header format (e.g., `en-US`, `fr-CA,fr;q=0.9`).
+
+**Using the `Auth0` class:**
+
+```js
+import Auth0 from 'react-native-auth0';
+
+const auth0 = new Auth0({
+ domain: 'YOUR_AUTH0_DOMAIN',
+ clientId: 'YOUR_AUTH0_CLIENT_ID',
+ // Set preferred language(s)
+ acceptLanguage: 'es-ES,es;q=0.9'
+});
+
+// All subsequent API calls made using this auth0 instance
+// (e.g., auth0.auth.passwordlessWithSMS())
+// will include the 'Accept-Language: es-ES,es;q=0.9' header.
+```
+
+**Using the Auth0Provider hook:**
+
+```js
+import { Auth0Provider } from 'react-native-auth0';
+
+const App = () => {
+ return (
+
+ {/* YOUR APP */}
+
+ );
+};
+
+export default App;
+
+// All subsequent API calls made via the useAuth0() hook derived
+// from this provider will include the 'Accept-Language: de-DE' header.
+
+```
+
## Authentication API
Unlike web authentication, we do not provide a hook for integrating with the Authentication API.
diff --git a/src/auth/index.ts b/src/auth/index.ts
index bf31eda0..710c8f3d 100644
--- a/src/auth/index.ts
+++ b/src/auth/index.ts
@@ -57,6 +57,15 @@ function convertTimestampInCredentials(
return { ...credentials, expiresAt };
}
+interface AuthOptions {
+ baseUrl: string;
+ clientId: string;
+ telemetry?: Telemetry;
+ token?: string;
+ timeout?: number;
+ acceptLanguage?: string; // Added acceptLanguage property
+}
+
/**
* Class for interfacing with the Auth0 Authentication API endpoints.
*
@@ -76,13 +85,7 @@ class Auth {
/**
* @ignore
*/
- constructor(options: {
- baseUrl: string;
- clientId: string;
- telemetry?: Telemetry;
- token?: string;
- timeout?: number;
- }) {
+ constructor(options: AuthOptions) {
this.client = new Client(options);
this.domain = this.client.domain;
diff --git a/src/auth0.ts b/src/auth0.ts
index 46038174..0b22d9ea 100644
--- a/src/auth0.ts
+++ b/src/auth0.ts
@@ -9,11 +9,22 @@ import addDefaultLocalAuthOptions from './utils/addDefaultLocalAuthOptions';
/**
* Auth0 for React Native client
*/
+
+interface Auth0ConstructorOptions {
+ domain: string;
+ clientId: string;
+ telemetry?: Telemetry;
+ token?: string;
+ timeout?: number;
+ localAuthenticationOptions?: LocalAuthenticationOptions;
+ acceptLanguage?: string; // <-- Changed: Accept language option
+}
+
class Auth0 {
public auth: Auth;
public webAuth: WebAuth;
public credentialsManager: CredentialsManager;
- private options;
+ private options: Auth0ConstructorOptions;
/**
* Creates an instance of Auth0.
@@ -24,20 +35,14 @@ class Auth0 {
* @param {String} options.token Token to be used for Management APIs
* @param {String} options.timeout Timeout to be set for requests.
* @param {LocalAuthenticationOptions} options.localAuthenticationOptions The options for configuring the display of local authentication prompt, authentication level (Android only) and evaluation policy (iOS only).
+ * @param {string} [options.acceptLanguage] Optional language tag (e.g., "en-US", "fr") to be sent in the Accept-Language header for all requests.
*/
- constructor(options: {
- domain: string;
- clientId: string;
- telemetry?: Telemetry;
- token?: string;
- timeout?: number;
- localAuthenticationOptions?: LocalAuthenticationOptions;
- }) {
- const { domain, clientId, ...extras } = options;
+ constructor(options: Auth0ConstructorOptions) {
+ const { domain, clientId, acceptLanguage, ...extras } = options;
const localAuthenticationOptions = options.localAuthenticationOptions
? addDefaultLocalAuthOptions(options.localAuthenticationOptions)
: undefined;
- this.auth = new Auth({ baseUrl: domain, clientId, ...extras });
+ this.auth = new Auth({ baseUrl: domain, clientId, acceptLanguage, ...extras });
this.webAuth = new WebAuth(this.auth, localAuthenticationOptions);
this.credentialsManager = new CredentialsManager(
domain,
diff --git a/src/hooks/auth0-provider.tsx b/src/hooks/auth0-provider.tsx
index f5e26417..b090d3b2 100644
--- a/src/hooks/auth0-provider.tsx
+++ b/src/hooks/auth0-provider.tsx
@@ -56,12 +56,21 @@ const finalizeScopeParam = (inputScopes?: string) => {
return Array.from(scopeSet).join(' ');
};
+interface Auth0ProviderProps {
+ domain: string;
+ clientId: string;
+ localAuthenticationOptions?: LocalAuthenticationOptions;
+ timeout?: number;
+ acceptLanguage?: string;
+}
+
/**
* Provides the Auth0Context to its child components.
* @param {String} domain Your Auth0 domain
* @param {String} clientId Your Auth0 client ID
* @param {LocalAuthenticationOptions} localAuthenticationOptions The local auth options
* @param {number} timeout - Optional timeout in milliseconds for authentication requests.
+ * @param {string} [acceptLanguage] Optional language tag (e.g., "en-US", "fr") to be sent in the Accept-Language header for all requests.
* @param {React.ReactNode} children - The child components to render within the provider.
*
* @example
@@ -76,16 +85,12 @@ const Auth0Provider = ({
clientId,
localAuthenticationOptions,
timeout,
+ acceptLanguage,
children,
-}: PropsWithChildren<{
- domain: string;
- clientId: string;
- localAuthenticationOptions?: LocalAuthenticationOptions;
- timeout?: number;
-}>) => {
+}: PropsWithChildren) => {
const client = useMemo(
- () => new Auth0({ domain, clientId, localAuthenticationOptions, timeout }),
- [domain, clientId, localAuthenticationOptions, timeout]
+ () => new Auth0({ domain, clientId, localAuthenticationOptions, timeout, acceptLanguage }),
+ [domain, clientId, localAuthenticationOptions, timeout, acceptLanguage]
);
const [state, dispatch] = useReducer(reducer, initialState);
@@ -446,6 +451,9 @@ Auth0Provider.propTypes = {
domain: PropTypes.string.isRequired,
clientId: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
+ acceptLanguage: PropTypes.string,
+ localAuthenticationOptions: PropTypes.object,
+ timeout: PropTypes.number
};
export default Auth0Provider;
diff --git a/src/management/users.ts b/src/management/users.ts
index b5af6994..f2c93e59 100644
--- a/src/management/users.ts
+++ b/src/management/users.ts
@@ -29,6 +29,14 @@ const attributes = [
'family_name',
];
+interface UsersClientOptions {
+ baseUrl: string;
+ telemetry?: Telemetry;
+ token?: string;
+ timeout?: number;
+ acceptLanguage?: string;
+}
+
/**
* Auth0 Management API User endpoints
*
@@ -42,12 +50,7 @@ class Users {
/**
* @ignore
*/
- constructor(options: {
- baseUrl: string;
- telemetry?: Telemetry;
- token?: string;
- timeout?: number;
- }) {
+ constructor(options: UsersClientOptions) {
this.client = new Client(options);
if (!options.token) {
throw new Error('Missing token in parameters');
diff --git a/src/networking/__tests__/index.spec.js b/src/networking/__tests__/index.spec.js
index 815e850d..3595d485 100644
--- a/src/networking/__tests__/index.spec.js
+++ b/src/networking/__tests__/index.spec.js
@@ -48,6 +48,12 @@ describe('client', () => {
it('should fail with no domain', () => {
expect(() => new Client()).toThrowErrorMatchingSnapshot();
});
+
+ it('should store acceptLanguage option if provided', () => {
+ const lang = 'fr-CA';
+ const client = new Client({ baseUrl, acceptLanguage: lang });
+ expect(client.acceptLanguage).toEqual(lang);
+ });
});
describe('requests', () => {
@@ -219,6 +225,74 @@ describe('client', () => {
});
});
+ describe('Accept-Language Header', () => {
+ const path = '/test-endpoint';
+ const mockResponse = { status: 200, body: { success: true } };
+ const defaultClient = new Client({
+ baseUrl,
+ telemetry: {name: 'react-native-auth0', version: '1.0.0'},
+ token: 'a.bearer.token',
+ });
+ beforeEach(fetchMock.restore);
+ it('should NOT set Accept-Language header if option is not provided', async () => {
+ fetchMock.getOnce(`https://${domain}${path}`, mockResponse);
+ await defaultClient.get(path);
+
+ const requestOptions = fetchMock.lastOptions();
+ const headers = requestOptions.headers;
+ expect(headers.get('Accept-Language')).toBeNull();
+ });
+
+ it('should set Accept-Language header if option IS provided', async () => {
+ const lang = 'es-ES,es;q=0.9';
+ const clientWithLang = new Client({
+ baseUrl,
+ telemetry: { name: 'react-native-auth0', version: '1.0.0' },
+ token: 'a.bearer.token',
+ acceptLanguage: lang, // Provide the option
+ });
+
+ fetchMock.getOnce(`https://${domain}${path}`, mockResponse);
+ await clientWithLang.get(path);
+
+ const requestOptions = fetchMock.lastOptions();
+ const headers = requestOptions.headers;
+ expect(headers.get('Accept-Language')).toEqual(lang);
+ });
+
+ it('should set Accept-Language header for POST requests if option is provided', async () => {
+ const lang = 'fr';
+ const clientWithLang = new Client({
+ baseUrl,
+ acceptLanguage: lang,
+ });
+ const body = { data: 'test' };
+
+ fetchMock.postOnce(`https://${domain}${path}`, mockResponse);
+ await clientWithLang.post(path, body);
+
+ const requestOptions = fetchMock.lastOptions();
+ const headers = requestOptions.headers;
+ expect(headers.get('Accept-Language')).toEqual(lang);
+ });
+
+ it('should set Accept-Language header for PATCH requests if option is provided', async () => {
+ const lang = 'de-DE';
+ const clientWithLang = new Client({
+ baseUrl,
+ acceptLanguage: lang,
+ });
+ const body = { data: 'update' };
+
+ fetchMock.patchOnce(`https://${domain}${path}`, mockResponse);
+ await clientWithLang.patch(path, body);
+
+ const requestOptions = fetchMock.lastOptions();
+ const headers = requestOptions.headers;
+ expect(headers.get('Accept-Language')).toEqual(lang);
+ });
+ });
+
describe('url', () => {
const client = new Client({
baseUrl,
diff --git a/src/networking/index.ts b/src/networking/index.ts
index bff45a31..b2bdf635 100644
--- a/src/networking/index.ts
+++ b/src/networking/index.ts
@@ -13,23 +13,27 @@ class Client {
public domain: string;
private bearer?: string;
private timeout: number;
+ private acceptLanguage?: string;
constructor(options: {
baseUrl: string;
telemetry?: Telemetry;
token?: string;
timeout?: number;
+ acceptLanguage?: string;
}) {
const {
baseUrl,
telemetry = {},
token,
timeout = 10000,
+ acceptLanguage,
}: {
baseUrl: string;
telemetry?: Telemetry;
token?: string;
timeout?: number;
+ acceptLanguage?: string;
} = options;
if (!baseUrl) {
throw new Error('Missing Auth0 domain');
@@ -51,6 +55,7 @@ class Client {
}
this.timeout = timeout;
+ this.acceptLanguage = acceptLanguage;
}
post(path: string, body: TBody) {
@@ -89,6 +94,10 @@ class Client {
headers.set('Content-Type', 'application/json');
headers.set('Auth0-Client', this._encodedTelemetry());
+ if (this.acceptLanguage) {
+ headers.set('Accept-Language', this.acceptLanguage);
+ }
+
const options: RequestInit = {
method,
headers,