Skip to main content
Each person connects their 1Password account to your product once. The flow follows the OAuth 2.0 authorization code flow with PKCE, with one addition: the first time a person connects, 1Password also returns an integration key in the URL fragment of your callback. You need both the tokens and the integration key to request or use that person’s logins. A connection lets you create access requests for that person. It doesn’t let you read anything until they approve a request.

The connection flow

Base URLs and endpoints

1Password has two hosts. The person’s browser goes to the web origin to sign in and consent. Your backend calls the API origin for tokens and revocation. Test against these production origins. The development environment, b5dev.eu, can’t show approval prompts with current 1Password app builds, and its tokens and integration keys don’t work on production.
The authorize path has no version prefix. /v1/oauth/authorize sends the person to the 1Password home page instead of the consent screen. The token path does have one: /oauth/token without /v1 returns 404.
Everyone authorizes at the environment’s web origin, regardless of their account address. A person whose account is at acme.1password.com still authorizes at https://my.1password.com. Don’t ask the person for their account address or build an origin from it.

Register your redirect URI

Register your callback on your OAuth client, then send it character for character in every authorize and token request. The console doesn’t show the registered value again, so record it when you create the client. A redirect URI must use https and a hostname, with no user info and no fragment. Custom URL schemes and http://localhost aren’t accepted. A native mobile app needs an https callback, such as a universal link or app link, or a web page that hands the result back to the app.

Start authorization

To send one link that also starts a sign-up for people who don’t have 1Password yet, and connects inside 1Password for iOS, see Build a connect link. It’s in development.
Redirect the person’s browser to the authorize endpoint:
string
required
Always code.
string
required
Your OAuth client ID. It isn’t a secret.
string
required
Exactly one of the redirect URIs registered on your client, URL-encoded.
string
required
The two scopes, space-delimited: brokered-credentials:request-access brokered-credentials:read. Encode the separating space as %20.
string
required
A unique, unpredictable value for this attempt. Bind it to the person’s pending connection and check it on the callback.
string
required
BASE64URL(SHA256(code_verifier)), where code_verifier is a high-entropy value you generate and keep on your server for this attempt.
string
required
Always S256. plain isn’t supported.
Assembled, an authorization URL looks like this:

The person signs in and consents

1Password signs the person in and shows the consent screen, which lists what your integration can and can’t do. The person accepts or declines. The consent screen appears for Individual and Family accounts. Other account types, including Business accounts, land on the 1Password home page, so don’t use your company’s Business account as a test user. The consent screen shows the name and icon from your OAuth client registration.

Handle the callback

When 1Password creates a new integration key, it redirects to your callback with the code and state in the query string and the integration_key in the fragment:
The query string can also carry iss, the issuer, and base_url, the API origin for the person’s environment. When the person connects again and their integration key is still active, the fragment is absent:
If the person declines:
Browsers never send the fragment to a server, so your callback has to be a page that runs in the browser. It reads the integration key, stores it, and only then clears it from the URL:
callback.ts
Then send the code and state to your backend. Validate state on the backend before you accept the result: reject a missing, mismatched, or replayed value. The code is single use and expires quickly, and it’s bound to the client, the person, their account, the scope, the redirect URI, and the PKCE challenge. A missing integration_key is a normal result, not an error. See When you receive a key.

Exchange the code

Your backend exchanges the code directly with the token endpoint:
Authenticate with HTTP Basic only. Don’t also send client_id in the body: the token endpoint rejects a request that uses both.
Response
The response can also include user_id. Store the access token and refresh token in your backend, and never expose the client secret or the code verifier to browser code. Treat the token strings as opaque.

Refresh the access token

Access tokens expire after 15 minutes. Refresh from your backend:
1Password returns a new access token and a new refresh token. The old refresh token stops working, so replace it atomically and serialize refresh requests per person to prevent competing token rotations. An invalid_grant response to a refresh means the connection has ended: ask the person to connect again.

Errors

Checklist

  • A Connect 1Password action in your product.
  • A backend endpoint that creates state, the code verifier, and the S256 code challenge, then starts the redirect.
  • An https callback, registered exactly on your OAuth client.
  • A callback page that reads integration_key from the fragment, stores it before anything else that can fail, then clears the fragment. It keeps the key out of analytics and error reporting.
  • Backend handling that validates state and rejects missing, mismatched, or replayed values.
  • A backend code exchange, with the client secret and code verifier never exposed to browser code.
  • Encrypted backend storage for tokens, with logs and error messages that redact secrets.
  • Refresh-token rotation, and a reconnect path for connections that expire or are revoked.
  • A Disconnect 1Password action. See Disconnect a user.