> ## Documentation Index
> Fetch the complete documentation index at: https://www.1password.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Approve in the browser

> Open 1Password.com in a popup from your web app, let the person approve the access request there, and receive their decision back on your page.

Partner preview

Your web app can present an access request to 1Password without sending the person to the 1Password app. Your page opens 1Password.com in a popup and passes it the access request reference you got when you created the request. The person signs in, chooses which logins to grant, and approves or denies. The popup posts the answer back to your page. Your page receives references to the granted logins, never a password.

> [!NOTE]
>
> Approval in the browser is in development and isn't available to test yet. You can build your side against this page now. 1Password will let you know in your shared Slack channel when the surface is ready to test end to end.

## How it works

1. Your page opens `https://my.1password.com/grant-access` in a popup, with your page's origin and the access request reference.
2. 1Password checks that the origin matches your OAuth client's redirect URI, and the person signs in to the account they connected.
3. The person grants or denies the request.
4. The popup posts one `grantBrokeredAccess` message to your page through `window.opener`, then closes.
5. You confirm the outcome with a status check from your backend.

## Before you start

**Serve your page from your redirect URI's origin.** 1Password answers only a page whose origin exactly matches the origin of your OAuth client's redirect URI: scheme, host, and port, with the path ignored. A client has one redirect URI, so serve your approval page from the same origin as your OAuth callback. If your redirect URI is `https://app.partner.example/oauth/callback`, open the popup from a page on `https://app.partner.example`.

**Get the access request reference.** It's the `access_request_reference` value in the `appLink` returned when you create the request, by the browser extension's `createAccessRequest` or by the SDK's [`accessRequests.create`](/agentic-autofill/partners/sdk#create-an-access-request).

```javascript theme={null}
const reference = new URL(appLink).searchParams.get("access_request_reference");
```

Pass the reference unchanged. Like the approval link, it carries your request text, so keep it out of logs and model context, and send it only to the signed-in person's own page over HTTPS.

## Open the popup

Open this URL in a popup, from a click handler:

```text theme={null}
https://my.1password.com/grant-access?origin=<your page's origin>&version=1#access_request_reference=<reference>
```

| Part | Where | Value |
| - | - | - |
| `origin` | Query | Exactly `window.location.origin` of the page that opens the popup. No path and no trailing slash. |
| `version` | Query | `1`. Any other value gets `unsupported_version`. |
| `access_request_reference` | Fragment | The reference from `appLink`. It's in the fragment so the browser never sends it to 1Password's servers. |

The host is the web origin, `https://my.1password.com`, for everyone, whatever the person's account address. See [Base URLs and endpoints](/agentic-autofill/partners/connect.md#base-urls-and-endpoints).

```javascript theme={null}
const url = new URL("/grant-access", "https://my.1password.com");
url.searchParams.set("origin", window.location.origin);
url.searchParams.set("version", "1");
url.hash = `access_request_reference=${reference}`;

const popup = window.open(url, "op-grant-access", "popup,width=460,height=760");
```

Two things make sure the answer reaches your page:

* **Keep `window.opener`.** 1Password posts the answer through it. Don't pass `noopener` or `noreferrer` to `window.open`, don't serve your page with `Cross-Origin-Opener-Policy: same-origin`, and don't redirect your own tab to the URL instead of opening a popup.
* **Call `window.open` directly in the click handler.** Calling it after an `await` can trip popup blockers. Create the access request first, then show an **Approve in 1Password** button that opens the popup.

In the popup, the person signs in to the account they connected, then grants or denies the request. After they decide, the popup closes itself. After an error, it stays open and explains what went wrong.

## Receive the answer

Listen for `message` events on the page that opened the popup. Check every message before you trust it, and ignore everything else.

```javascript theme={null}
window.addEventListener("message", (event) => {
  if (event.source !== popup) return; // From the popup you opened
  if (event.origin !== "https://my.1password.com") return; // From 1Password
  const data = event.data;
  if (data?.name !== "grantBrokeredAccess") return; // The answer to your request
  if (data.accessRequestId !== undefined && data.accessRequestId !== accessRequest.id) return;
  if (data.accessRequestId === undefined && data.response?.status !== "error") return;

  handleResponse(data.response);
});
```

The popup posts at most one `grantBrokeredAccess` message, and your page never sends anything to the popup. The message has this shape:

```typescript theme={null}
interface GrantBrokeredAccessMessage {
  name: "grantBrokeredAccess";
  accessRequestId?: string; // Absent only on errors raised before 1Password could read the request
  response:
    | { status: "authorized"; accessRequestStatus: string } // JSON, as a string. See "The authorized payload".
    | { status: "denied" }
    | { status: "cancelled" }
    | { status: "error"; error: ErrorCode };
}
```

| `status` | When |
| - | - |
| `authorized` | The person approved, and 1Password recorded the grant. |
| `denied` | The person denied the request. |
| `cancelled` | While the prompt was showing, the person closed the popup, reloaded or navigated away, or didn't decide within 2 minutes. The request stays `pending`, so you can open the popup again with the same reference. |
| `error` | 1Password couldn't complete the request. See [Errors](#errors). |

The 2-minute timer starts when the prompt appears, so signing in doesn't use it up. If the person closes the popup before the prompt appears, while signing in or loading, the popup posts nothing. Watch `popup.closed` to catch that case.

### The `authorized` payload

`accessRequestStatus` is a JSON string. Parse it to get the access request's state and the granted logins:

```json theme={null}
{
  "version": 1,
  "payload": {
    "state": "resolved",
    "resolved": [
      {
        "entryId": "<entry-id>",
        "reference": {
          "reference": "accounts/<account>/capability/login/configurations/<configuration-id>"
        }
      },
      {
        "entryId": null,
        "reference": {
          "reference": "accounts/<account>/capability/login/configurations/<configuration-id>"
        }
      }
    ]
  }
}
```

The payload carries references only, never item data. `entryId` names the request entry the login satisfies, and is `null` for a login the person added beyond the ones you asked for. Match on `entryId`, never on position. Each reference is nested as `reference.reference`: pass that inner string, unchanged, to the extension's [`fillCredential`](/agentic-autofill/partners/fill#fill-a-granted-login) or to the SDK's [read methods](/agentic-autofill/partners/sdk.md#read-a-granted-login). See [Read what the person granted](/agentic-autofill/partners/approval.md#read-what-the-person-granted).

### Errors

When `status` is `error`, `error` is one of these codes:

| `error` | Meaning | What to do |
| - | - | - |
| `invalid_request` | The reference is damaged or breaks a limit, or the person signed in to a different account than the one they connected. | Check that the reference is unchanged, or ask the person to sign in to the account they connected. |
| `unsupported_version` | `version` isn't `1`. | Send `version=1`. |
| `feature_not_enabled` | The person's account doesn't have brokered access turned on. | Don't retry. |
| `internal_error` | 1Password hit an unexpected error. The request may still be `pending`. | Check the request's status. If it's still `pending`, open the popup again with the same reference. |
| `request_failed` | The person approved, but 1Password couldn't record the grant. | Create a new request. |
| `account_not_found` | The person has no account, or signed in to a different one. | Ask the person to sign in to the account they connected. |
| `request_unavailable` | The request wasn't found, or was already answered. | Create a new request. |

`account_not_found` and `request_unavailable` aren't sent yet. Those cases arrive as `internal_error` or `invalid_request` for now. Handle every code anyway, and treat a code you don't recognize like `internal_error`.

## Confirm on your backend

Treat the popup's answer as a signal to check the request's status, not as the result. The status from the SDK's `accessRequests.getStatus`, or the extension's `getAccessRequestStatus`, is authoritative, and the two can differ:

* **`cancelled`, but granted.** If the person closes the popup while their approval is being submitted, your page hears `cancelled`, but the grant can still complete.
* **`denied`, but still `pending`.** 1Password posts `denied` even if it couldn't record the denial, so the request can stay `pending`.

When the popup answers, or closes without answering, check the status from your backend and act on that. See [Wait for the decision](/agentic-autofill/partners/approval.md#wait-for-the-decision).

## If no answer arrives

| Symptom | Likely cause |
| - | - |
| The popup shows "Unable to share access" and posts nothing | Your page isn't on your redirect URI's origin, or `origin` isn't exactly `window.location.origin`. |
| No message, and no error in the popup | `window.opener` was lost: `noopener`, `Cross-Origin-Opener-Policy: same-origin`, or a redirect instead of a popup. |
| The popup closed with no message | The person closed it while signing in or loading. Check the request's status. |


## Related topics

- [Authorize SSH requests with 1Password](/ssh/agent/authorization.md)
- [Use the 1Password SSH agent with WSL](/ssh/integrations/wsl.md)
- [SSH agent forwarding with 1Password](/ssh/agent/forwarding.md)
