|
11 | 11 | 9. [Why doesn't `await authorize()` work on the web? How do I handle login?](#9-why-doesnt-await-authorize-work-on-the-web-how-do-i-handle-login) |
12 | 12 | 10. [Why do my users get logged out frequently? How do I keep them logged in?](#10-why-do-my-users-get-logged-out-frequently-how-do-i-keep-them-logged-in) |
13 | 13 | 11. [How can I prompt users to the login page versus signup page?](#11-how-can-i-prompt-users-to-the-login-page-versus-signup-page) |
14 | | -12. [What is DPoP and should I enable it?](#12-what-is-dpop-and-should-i-enable-it) |
15 | | -13. [How do I migrate existing users to DPoP?](#13-how-do-i-migrate-existing-users-to-dpop) |
16 | | -14. [How do I know if my tokens are using DPoP?](#14-how-do-i-know-if-my-tokens-are-using-dpop) |
17 | | -15. [What happens if I disable DPoP after enabling it?](#15-what-happens-if-i-disable-dpop-after-enabling-it) |
| 14 | +12. [Why does `getCredentials()` return an opaque access token on web instead of a JWT?](#12-why-does-getcredentials-return-an-opaque-access-token-on-web-instead-of-a-jwt) |
| 15 | +13. [What is DPoP and should I enable it?](#13-what-is-dpop-and-should-i-enable-it) |
| 16 | +14. [How do I migrate existing users to DPoP?](#14-how-do-i-migrate-existing-users-to-dpop) |
| 17 | +15. [How do I know if my tokens are using DPoP?](#15-how-do-i-know-if-my-tokens-are-using-dpop) |
| 18 | +16. [What happens if I disable DPoP after enabling it?](#16-what-happens-if-i-disable-dpop-after-enabling-it) |
18 | 19 |
|
19 | 20 | ## 1. How can I have separate Auth0 domains for each environment on Android? |
20 | 21 |
|
@@ -366,7 +367,103 @@ const signup = async () => { |
366 | 367 | } |
367 | 368 | ``` |
368 | 369 |
|
369 | | -## 12. What is DPoP and should I enable it? |
| 370 | +## 12. Why does `getCredentials()` return an opaque access token on web instead of a JWT? |
| 371 | + |
| 372 | +When calling `getCredentials()` on the **web platform**, you may receive an opaque access token (a token with ".." in the middle that doesn't parse as a JWT) instead of a JWT, even though you specified an `audience` during the initial `authorize()` call. |
| 373 | + |
| 374 | +**Root Cause:** |
| 375 | + |
| 376 | +On web, credentials are stored in browser storage (sessionStorage/localStorage). When you call `getCredentials()` without specifying the `audience` parameter, the SDK returns the default opaque token instead of the API-specific JWT access token. |
| 377 | + |
| 378 | +**Solution:** |
| 379 | + |
| 380 | +You must pass the `audience` parameter to **both** `authorize()` and `getCredentials()`: |
| 381 | + |
| 382 | +```javascript |
| 383 | +import { useAuth0 } from 'react-native-auth0'; |
| 384 | + |
| 385 | +const AUDIENCE = 'https://your-api.example.com'; |
| 386 | + |
| 387 | +function App() { |
| 388 | + const { authorize, getCredentials } = useAuth0(); |
| 389 | + |
| 390 | + // ✅ CORRECT: Specify audience during login |
| 391 | + const onLogin = async () => { |
| 392 | + await authorize({ |
| 393 | + audience: AUDIENCE, |
| 394 | + scope: 'openid profile email offline_access' |
| 395 | + }); |
| 396 | + }; |
| 397 | + |
| 398 | + // ✅ CORRECT: Specify audience when retrieving credentials |
| 399 | + const onGetCredentials = async () => { |
| 400 | + const credentials = await getCredentials( |
| 401 | + 'openid profile email offline_access', |
| 402 | + 0, |
| 403 | + { audience: AUDIENCE } // ← Must include audience here! |
| 404 | + ); |
| 405 | + console.log('JWT Access Token:', credentials.accessToken); |
| 406 | + }; |
| 407 | + |
| 408 | + return ( |
| 409 | + <View> |
| 410 | + <Button onPress={onLogin} title="Log In" /> |
| 411 | + <Button onPress={onGetCredentials} title="Get Credentials" /> |
| 412 | + </View> |
| 413 | + ); |
| 414 | +} |
| 415 | +``` |
| 416 | + |
| 417 | +**Why this happens:** |
| 418 | + |
| 419 | +- **During `authorize()`**: The `audience` tells Auth0 to issue a JWT for your API |
| 420 | +- **During `getCredentials()`**: On web, the audience must be re-specified to retrieve the correct token type |
| 421 | +- **Platform difference**: Native platforms (iOS/Android) store credentials with all parameters and retrieve as-is, but web may need to refresh tokens |
| 422 | + |
| 423 | +**Best Practice:** |
| 424 | + |
| 425 | +Define your auth configuration once and reuse it: |
| 426 | + |
| 427 | +```javascript |
| 428 | +const AUTH_CONFIG = { |
| 429 | + audience: 'https://your-api.example.com', |
| 430 | + scope: 'openid profile email offline_access' |
| 431 | +}; |
| 432 | + |
| 433 | +// Login |
| 434 | +await authorize(AUTH_CONFIG); |
| 435 | + |
| 436 | +// Get credentials later (include audience in parameters) |
| 437 | +await getCredentials(AUTH_CONFIG.scope, 0, { |
| 438 | + audience: AUTH_CONFIG.audience |
| 439 | +}); |
| 440 | +``` |
| 441 | + |
| 442 | +**Using the class-based API:** |
| 443 | + |
| 444 | +```javascript |
| 445 | +const auth0 = new Auth0({ |
| 446 | + domain: 'YOUR_DOMAIN', |
| 447 | + clientId: 'YOUR_CLIENT_ID' |
| 448 | +}); |
| 449 | + |
| 450 | +// Login |
| 451 | +await auth0.webAuth.authorize({ |
| 452 | + audience: 'https://your-api.example.com', |
| 453 | + scope: 'openid profile email offline_access' |
| 454 | +}); |
| 455 | + |
| 456 | +// Get credentials (must include audience) |
| 457 | +const credentials = await auth0.credentialsManager.getCredentials( |
| 458 | + 'openid profile email offline_access', |
| 459 | + 0, |
| 460 | + { audience: 'https://your-api.example.com' } |
| 461 | +); |
| 462 | +``` |
| 463 | + |
| 464 | +> **Note**: This behavior is specific to the web platform. On iOS and Android, the `audience` parameter is automatically preserved from the initial `authorize()` call. |
| 465 | +
|
| 466 | +## 13. What is DPoP and should I enable it? |
370 | 467 |
|
371 | 468 | **DPoP** (Demonstrating Proof-of-Possession) is an OAuth 2.0 security extension ([RFC 9449](https://datatracker.ietf.org/doc/html/rfc9449)) that cryptographically binds access and refresh tokens to a specific device using public/private key pairs. This means that even if an access token is stolen (e.g., through XSS or network interception), it cannot be used from a different device because the attacker won't have the private key needed to generate valid DPoP proofs. |
372 | 469 |
|
@@ -397,7 +494,7 @@ const auth0 = new Auth0({ |
397 | 494 | }); |
398 | 495 | ``` |
399 | 496 |
|
400 | | -## 13. How do I migrate existing users to DPoP? |
| 497 | +## 14. How do I migrate existing users to DPoP? |
401 | 498 |
|
402 | 499 | When you enable DPoP in your app, existing users will still have Bearer tokens from their previous sessions. DPoP only applies to **new sessions** created after it's enabled. Here's how to handle the migration: |
403 | 500 |
|
@@ -589,7 +686,7 @@ if (credentials.tokenType !== 'DPoP') { |
589 | 686 | 3. **Communicate with users**: Explain why re-authentication is necessary |
590 | 687 | 4. **Handle errors gracefully**: Network issues or user cancellation should be handled appropriately |
591 | 688 |
|
592 | | -## 14. How do I know if my tokens are using DPoP? |
| 689 | +## 15. How do I know if my tokens are using DPoP? |
593 | 690 |
|
594 | 691 | You can check the `tokenType` property of the credentials returned by `getCredentials()`: |
595 | 692 |
|
@@ -670,7 +767,7 @@ if (headers.DPoP) { |
670 | 767 | } |
671 | 768 | ``` |
672 | 769 |
|
673 | | -## 15. What happens if I disable DPoP after enabling it? |
| 770 | +## 16. What happens if I disable DPoP after enabling it? |
674 | 771 |
|
675 | 772 | If you disable DPoP after enabling it (by setting `useDPoP: false`), here's what happens: |
676 | 773 |
|
|
0 commit comments