Connected Accounts
Let people sign in with more than one provider, and remove one they no longer use, from the Security screen.
A connected account is a social provider linked to someone's account in your app: the Google, GitHub or Microsoft account they can sign in with. Everyone who has signed in with a provider already has one. Connecting more lets a person sign in whichever way is to hand, and lets someone who signed up with Google stop depending on that one account.
Step by step, with screenshots: The account security screens.
People manage their own from the Connected accounts section of the SDK's Security screen:
import { useSaaSAuth } from '@buildbase/sdk/react';
function SecurityButton() {
const { openWorkspaceSettings } = useSaaSAuth();
return (
<button onClick={() => openWorkspaceSettings('security')}>Security</button>
);
}The section lists the providers the person has connected, with a remove action on each, and offers the social providers ticked on your client that they have not connected yet. A provider you have not set up, or not ticked on the client, is never offered. Nothing needs registering at the provider: connecting uses the same callback URL as signing in.
The section calls the routes below, so a screen of your own, or an app on an SDK release without the section, can do the same.
Connecting a provider
The person picks a provider, signs in there, and comes back to the page they started from with the Security screen open and the outcome on it. A provider can be connected once: a person with a Google account connected cannot add a second one without removing the first.
An identity that belongs to someone else is refused. If the Google account the person signed in with is already connected to a different account in your app, it is not moved, shared or merged. The person comes back with link_conflict and both accounts stay as they were. Merging two people's accounts is something to do on purpose, not as a side effect of a sign-in.
When connecting does not work, the person comes back with error and one of these reasons, and the Security screen says what happened:
reason | Meaning |
|---|---|
link_conflict | That identity is connected to another account here. Nothing changed. |
provider_error | The provider answered with an error, or the person cancelled there. |
provider_failed | The provider sent the person back, but exchanging the code or reading the profile failed. |
session_ended | The session that started the connection ended before the person came back, so nothing was linked. |
A guest who connects a provider becomes an account. For a guest, connecting Google or another provider turns the guest into an ordinary account with the provider's address, keeping the user ID and data. That needs the address to be verified by the provider, free here and allowed to sign up; otherwise nothing is linked.
Wallets
A crypto wallet is a connected account too, listed with a short address instead of an email. While your client offers wallet sign-in, the section shows Connect an Ethereum wallet and Connect a Solana wallet: the wallet in the browser signs a message, and the address is linked to the account. It counts as a way in, and removing it follows the same two rules. Connecting has to happen on your app's own page, on one of its allowed origins, and a guest creates an account before connecting a wallet.
Removing a provider
Removing a connected account means the person can no longer sign in with it. They can connect it again later. Two rules guard it.
A recent sign-in. Removing one needs a session that signed in, or re-confirmed, in the last 10 minutes. An older session is refused with reauth_required and "For your security, sign in again to remove a connected account." The Security screen offers Confirm it's you, which re-confirms on the hosted sign-in page and brings the person back to the same place in the same session. See Confirm it's you. The refusal is a 403, not a 401, so the session stays signed in.
Never the last way in. Removing the only way someone can sign in would lock them out, so it is refused with last_sign_in_method and "This is the only way you can sign in. Add another sign-in method before removing it." The ways that count:
- each connected account
- each passkey
- a password the person set themselves, while your client offers email and password sign-in
- a verified email address, while your client offers magic links or email sign-in codes
- a verified phone number, while your client offers phone sign-in
Removing a passkey (DELETE /api/v1/public/passkeys/:id) is weighed the same way, so the last way in cannot be removed as a passkey either.
An account made through a social provider has a password nobody knows until the person sets one, so it does not count until they do. On the Security screen, the last way in keeps its remove button switched off, with that sentence beside it.
Events
| Event | When | data |
|---|---|---|
user.account_linked | A person connected a provider | actorId, userId, provider, accountId |
user.account_unlinked | A person removed a connected provider | actorId, userId, provider, accountId |
provider is the provider's id: google, linkedIn, github, microsoft or apple, custom:<methodId> for a custom provider, and ethereum or solana for a wallet. Subscribe to either as a webhook event.
The API
Three routes under /api/v1/public/accounts, authorized by the signed-in person's session in the x-session-id header, as the SDK sends it. Errors are { success: false, message, reason }; show message as it is.
GET /api/v1/public/accounts answers:
{
"accounts": [
{
"_id": "6650f1c2a1b2c3d4e5f60718",
"provider": "google",
"label": "Google",
"icon": "https://api.console.buildbase.app/api/images/auth/google.svg",
"createdAt": "2026-10-14T09:12:00.000Z",
"canUnlink": true
}
],
"available": [
{
"provider": "github",
"label": "GitHub",
"icon": "https://api.console.buildbase.app/api/images/auth/github.svg",
"methodId": "6650f1c2a1b2c3d4e5f60720"
}
],
"signInWays": 2,
"recentSignIn": true
}canUnlink is false on an account that is the last way in, signInWays counts the ways listed above, and recentSignIn says whether this session signed in or re-confirmed in the last 10 minutes. A wallet row carries its short address as detail, and while your client offers wallet sign-in the answer carries wallet: { ethereumChainIds, solana }.
POST /api/v1/public/accounts with { "methodId": "...", "redirect": "https://app.example.com/settings" } answers { "url": "..." }. Send the browser there. redirect is where the person comes back, with ?linked=<provider> on success or error and reason on failure. It is checked like any sign-in's redirect: it has to be on an origin registered on your client. <provider> is the same provider value the list carries, so a custom provider comes back as custom:<methodId> and matches its row.
| Refusal | When |
|---|---|
400 | redirect is missing, or not on a registered origin |
404 method_unavailable | The method is not a social method on this client |
409 already_linked | The person already has that provider connected |
503 provider_failed | A custom provider's discovery cannot be read now |
DELETE /api/v1/public/accounts/:id removes one.
| Refusal | When |
|---|---|
403 reauth_required | No sign-in or re-confirmation in 10 minutes |
409 last_sign_in_method | It is the person's only way to sign in |
404 | That connected account is not this person's |
Next steps
- Social Providers - set up the providers people can connect.
- Importing Users - bring people over with their Google, GitHub, Microsoft, Apple and LinkedIn sign-ins already connected.
- Pre-built UI - every settings screen the SDK ships.