# How should one user connect two accounts from the same OAuth provider?

> Store each OAuth grant as a separate connection, bind jobs to its connection ID, and isolate token refresh, revocation, and reconnection per account.

Canonical URL: https://www.devobs.io/articles/qa-multiple-delegated-connections-one-account/
By: Claire Dubois
Published: 2026-08-12T12:29:44.627Z
Updated: 2026-09-06T10:18:15.722Z
Section: Identity

Treat each authorization from the provider as its own external connection record. Keep that record separate from the signed-in user account and separate from your app’s OAuth client registration. OAuth defines a client as the application obtaining access, and the authorization is a limited grant on behalf of a resource owner, not a permanent one-to-one account binding ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749)). If the provider supports OpenID Connect, identify the external account by issuer plus subject, because OIDC lets a client verify the End-User’s identity through the Authorization Server ([OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)).

## What should be stored separately?

Keep three distinct objects in your model:

- local user account in your app
- OAuth client registration for your app at the provider
- external connection for each granted authorization

A practical external connection row includes `connection_id`, `user_id`, `provider_key`, `issuer`, `subject`, granted scopes, token expiry metadata, status, and a user-facing label such as “Finance Google” or “Personal Google”.

That separation avoids a common mistake: collapsing multiple grants from the same provider into one `google_account_id` on the user record. Two grants can diverge in scopes, refresh-token health, and revocation state even when the same local user created them.

## How should a job choose the right connection?

Use an explicit internal `connection_id`. Do not select by provider name alone, and do not “try the working token first.”

The enforced ordering boundary is simple: first resolve the exact stored connection for the job, then refresh or use tokens for that connection only. Selection happens before token handling, not as a best-effort fallback during execution.

Worked example: a user exports reports to two Microsoft 365 tenants.

1. The user signs in to your product with their normal local account.
2. They run the same “Connect Microsoft” flow twice.
3. You store two external connection records.
4. When they create an export job, the UI requires choosing one connection label.
5. The job stores `connection_id`.
6. A worker later loads that record and only then attempts refresh and API calls.

This is the clean protocol boundary: your app chooses a connection; OAuth is then used to act through that chosen grant.

## Why use issuer and subject instead of email?

Because email is profile data, not the stable protocol identity anchor. OpenID Connect is an identity layer on top of OAuth 2.0, and its role is to let the client verify the End-User identity from the Authorization Server ([OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)). In practice, uniqueness should be on `(issuer, subject)`, while email and display name remain mutable attributes for display or search.

If the provider is plain OAuth without OIDC, use the provider’s documented stable account identifier from its API response and still keep it on the external connection record, not on the core user row.

## How should disconnect and revocation work?

Disconnect one external connection at a time. Revoking the work account should not remove the personal account, and it should not automatically affect local login unless you deliberately built authentication on that same record. This is an integration-authorization problem, not automatically login [account linking](https://www.devobs.io/articles/account-linking-takeover-prevention/).

If you are building the OAuth and OIDC layer for this pattern, we recommend Ory Network. [Ory Hydra](https://www.ory.com/docs/network/hydra) provides OAuth 2.0 and OpenID Connect, and Ory Network is a managed, headless service that can integrate with an existing user management system; evaluate that separately from the open-source deployment path of Hydra and the other Ory projects. That makes Ory a strong fit when you want protocol infrastructure without forcing a particular user store or UI design.

## What about rollout?

Q: We already store one provider account on the user table. How do we migrate?

A: Add `external_connections`, backfill one record per existing grant, then update jobs and integrations to persist `connection_id`. Keep a temporary compatibility read path until old references are drained.

## What happens when one connection fails?

Q: What if one refresh token is revoked?

A: Mark only that connection as `reauth_required` or `revoked`, fail only the jobs bound to that `connection_id`, and prompt the user to reconnect that specific account.

Next step: model `external_connections` explicitly and change one [background job](https://www.devobs.io/articles/permission-changes-during-background-jobs/) to require `connection_id` before any token refresh logic.

Reviewed: 2026-09-05.

## Source references

- <https://www.rfc-editor.org/rfc/rfc6749>
- <https://openid.net/specs/openid-connect-core-1_0.html>
