Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/android-authorization-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-app-auth": minor
---

Add `resumePendingAuthorize` to recover Android browser authorization after process termination, preserving client authentication and skipped code exchanges. Move Custom Tab preparation off the UI thread.
5 changes: 5 additions & 0 deletions .changeset/android-result-delivery-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-app-auth": patch
---

Prevent a delayed or repeated Android activity callback from replaying an already consumed authorization result, and preserve the completed flow's exchange options when another login starts concurrently.
53 changes: 53 additions & 0 deletions docs/docs/usage/authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,56 @@ This is the result from the auth server:
- **scopes** - ([`string`]) the scopes the user has agreed to be granted
- **authorizationCode** - (`string`) the authorization code (only if `skipCodeExchange=true`)
- **codeVerifier** - (`string`) the codeVerifier value used for the PKCE exchange (only if both `skipCodeExchange=true` and `usePKCE=true`)

## Resuming an interrupted authorization (Android)

On Android, the OS can kill your app's process while the user is away in the browser completing
the login (e.g. under memory pressure). When the user returns, React Native drops the resulting
activity result because it arrives before the JS context is ready
([facebook/react-native#30277](https://github.com/facebook/react-native/issues/30277)), so the
in-flight `authorize()` promise never resolves or rejects.

`resumePendingAuthorize` lets you recover from this: it claims the stashed result and completes
the token exchange. It resolves `null` when there is nothing to resume, so it's safe to call
unconditionally on every app start, and it always resolves `null` on iOS.

```js
import { resumePendingAuthorize } from 'react-native-app-auth';

const result = await resumePendingAuthorize(config);
if (result) {
// an interrupted authorize() was completed
}
```

This requires your `MainActivity` to forward the raw activity result to
`RNAppAuthModule` before React Native's normal handling has a chance to drop it:

```kotlin
import android.content.Intent
import com.rnappauth.RNAppAuthModule
Comment thread
Copilot marked this conversation as resolved.

class MainActivity : ReactActivity() {
// ...

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == RNAppAuthModule.AUTHORIZATION_REQUEST_CODE) {
RNAppAuthModule.stashAuthorizationResult(data)
}
super.onActivityResult(requestCode, resultCode, data)
}
}
```

#### `config`

Pass the original authorization configuration when resuming. `additionalParameters`,
`clientSecret`, `clientAuthMethod`, `skipCodeExchange`, `dangerouslyAllowInsecureHttpRequests`,
`customHeaders`, and `connectionTimeoutSeconds` keep the same meaning as in `authorize`.
With `skipCodeExchange: true`, recovery returns the authorization code and original PKCE
verifier without exchanging the code. Client secrets and exchange options are supplied by
your app; they are not saved in the browser result.

Call this before starting a new authorization. A pending result is claimed once: subsequent
calls return `null`, including after an exchange failure. Start a new authorization if recovery
fails. A live `authorize()` owns its own result and is not recovered by this API.
Loading
Loading