Skip to content

Connected accounts

Let users connect their GitHub, Google Drive, Slack or other accounts once, and let your APIs and MCP servers obtain those accounts’ tokens through Authio.

Agents and APIs often need to act in another service as the user: open an issue on GitHub, read a file in Google Drive. Connected accounts keep that access in Authio. The user connects their account once, Authio stores and renews its tokens, and your resource server exchanges the token it received from the user for the third-party access token whenever it needs one. The third-party refresh token never leaves Authio.

A connection provider is the OAuth app your realm registers at the external service. Each realm uses its own app, so what the users of one realm authorize never reaches another.

Connected accounts must be included in your plan. The plan also sets how many accounts can be connected at once and how many token exchanges can be made each month.

How it works

  1. You register a connection provider

    The OAuth app you created at GitHub, Google or another service, registered in the realm.

  2. The user connects their account

    On Authio’s hosted pages, signed in to your application, the user authorizes the service. Authio stores the tokens encrypted.

  3. Your resource server exchanges the user’s token

    When the user calls your API or MCP server, it presents the access token it received to Authio’s token endpoint with a token exchange request.

  4. Authio returns the provider’s access token

    Renewed first if it is about to expire. Your server calls the service with it, on the user’s behalf.

Register a connection provider

  1. Create the app at the provider

    At the external service, create an OAuth app and register this callback (redirect) URL on it:

    Callback URLPlain text
    https://authio.api.hyperio.tech/realms/acme/connections/callback
  2. Register it in Authio

    Open Connection Providers, choose New connection provider, pick the type and fill in the app’s details:

    FieldRules
    Alias2 to 64 lowercase letters, digits and hyphens, such as google-drive. Unique in the realm and fixed once created, because connect links and exchange requests use it.
    Display nameUp to 128 characters, shown to users.
    Client ID and secretThose of the app you created. The secret is stored encrypted and never shown again.
    ScopesWhat a connection asks the service for. Left empty, the type’s default scopes apply. Separate them with spaces; commas are accepted too.
    Allowed resource serversThe resource servers that may exchange tokens from this provider. Empty means every resource server of the realm, including ones created later.
  3. Keep it active

    Only an active provider accepts new connections and exchanges. Deactivating one keeps the accounts already connected through it.

Provider types

TypeDefault scopesToken renewal
GitHub AppNone: permissions are set on the appWhen the app uses expiring tokens
GitHub OAuth Appread:user user:emailNot needed: its tokens do not expire
GitLabread_api read_userYes
Google Driveopenid email https://www.googleapis.com/auth/drive.fileYes
Microsoft OneDriveoffline_access User.Read Files.ReadYes
Dropboxaccount_info.read files.metadata.read files.content.readYes
Slackchannels:read users:readYes
NotionNone: the user picks pages when consentingYes
Atlassianread:me read:jira-work offline_accessYes
LinearreadYes
Custom OAuth 2.0Yours. Requires https authorization, token and user info endpoints; revocation is optional.Yes

Let users connect their accounts

Connecting happens on Authio’s hosted pages, for a user signed in to the realm through your client. Link them to one of two addresses on https://authio.api.hyperio.tech:

AddressWhat it does
/realms/acme/clients/acme-web/account/connectionsThe user’s connected accounts page: the realm’s active providers, the accounts already connected, the default account of each provider, and buttons to connect and disconnect.
/realms/acme/clients/acme-web/connections/google-drive/connectStarts connecting one provider right away. Accepts return_to, a path under /realms/acme/ to land on afterwards, and context, the ID of an organization the connection is made for.
  • Without a session for the realm and the client, the user lands on an error page asking them to sign in first.
  • The flow uses PKCE (S256) with every provider that supports it, and a single-use state valid for 15 minutes.
  • Back from the provider, the landing page receives connected=google-drive on success, or connection_error=ConnectionDenied when the user refused.
  • Reconnecting the same external account renews its tokens and puts it back in force. The first account connected for a provider becomes the default.

Get the third-party token

Only a resource server can exchange. The caller authenticates as the introspection client of an active resource server — a confidential client with the Token Exchange grant enabled — and the user’s access token must be addressed (aud) to one of its resource servers.

RequestBash
curl -X POST https://authio.api.hyperio.tech/realms/acme/protocol/openid-connect/token \
  -u "acme-api:YOUR_CLIENT_SECRET" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "subject_token=$USER_ACCESS_TOKEN" \
  -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "requested_token_type=urn:authio:params:oauth:token-type:connection-access-token" \
  -d "connection_provider=google-drive"
  • Pass connection_id to pick one connection when the user has several. Without it, Authio uses the default connection, or the only active one.
  • A token that expires within 60 seconds is renewed with the provider before it is returned, and concurrent requests never spend the same refresh token twice.
  • expires_in is left out when the provider’s token does not expire. The provider’s refresh token is never part of the response.

When there is no token to return

Errors come in the OAuth format, with error_code and, when they help, connect_uri, connection_provider and connections:

ResponseJSON
{
  "error": "connection_required",
  "error_code": "ConnectionRequired",
  "connection_provider": "google-drive",
  "connect_uri": "https://authio.api.hyperio.tech/realms/acme/clients/acme-web/connections/google-drive/connect"
}
ErrorWhat to do
connection_requiredThe user has no usable connection — never connected, or the provider refused a renewal. Send them to connect_uri, which points at the client they signed in with.
connection_selection_requiredSeveral active connections and no default. Pick one from connections (id, label, external_login) and repeat with connection_id.
connection_policy_violationThe account no longer meets the organization’s policy. The user has to connect an account the policy accepts.
invalid_targetThe provider does not exist, is inactive or does not allow this resource server.
invalid_grantThe user’s token is not active, is not addressed to one of your resource servers or names no user.
unauthorized_clientThe caller is not the introspection client of an active resource server.
access_deniedThe plan does not include connected accounts, or the month’s exchanges are used up.
temporarily_unavailableThe provider failed or did not answer in time. The connection is left as it was; try again later.

Organizations and connection policies

Every connection is made for a context: the user personally, or one of their organizations. A token with org_id only reaches connections made for that organization; a token without it only reaches personal ones.

Each organization can set connection policies on its People tab, saying which external accounts it accepts:

  • Allowed email domains, matched exactly: acme.com does not accept br.acme.com. Google accounts are checked by their Workspace domain, so a @gmail.com account is refused.
  • Allowed GitHub organizations, for GitHub providers: the account must be an active member of one of them.
  • A provider’s own policy replaces the organization’s general one; the two never add up.
  • With Require the organization’s context off, a token for the organization may fall back to the user’s personal account of that provider, as long as the policy accepts it.

Policies are applied when the account is connected and again on every exchange. A connection that stops meeting its policy is marked as a policy violation and stays unusable until the user connects an account the policy accepts — relaxing the policy later does not bring it back.

Disconnect and revoke

  • Users disconnect an account on their connected accounts page. Authio asks the provider to revoke the tokens, as far as each provider allows, and deletes the connection.
  • Administrators revoke a user’s connection from the Credentials tab of the user in Users. The tokens are discarded and the connection stays listed as revoked until the user connects the same account again.
  • Deleting a provider revokes and deletes every connection made through it.
Connection stateMeaning
ActiveHas a token that can be used or renewed.
NeedsReauthThe provider refused a renewal, or the token expired with no way to renew it. The user has to connect again.
PolicyViolationThe account no longer meets the organization’s connection policy.
RevokedAn administrator revoked it.

Security notes

  • Access and refresh tokens are encrypted at rest. The admin API never returns them; it shows dates, granted scopes and the external account.
  • Only the introspection client of a resource server can exchange, and only a user token addressed to one of its servers.
  • Restrict each provider to the resource servers that need it: an empty list means all of them.
  • Every connection keeps its own trail — connected, used, refreshed, refresh failed, disconnected, revoked, policy violation — on the provider’s Access tab, and every exchange, delivered or refused, is recorded in the access log as a token exchange.

Next steps