Changelog
Guides

Link and unlink GitHub or Google accounts

Let a signed-in user link a GitHub or Google login to their account, list linked logins, and unlink one.

A signed-in user can add a GitHub or Google login to an existing account, then sign in with it later. These routes come from MapExternalAccountEndpoints in the AuthEndpoints.External.OAuth preview package. The facade does not map them.

Map the account routes

Register the providers as in Register with GitHub or Google. Then map the account routes on the same group as the sign-in routes:

var external = app.MapGroup("/auth/external");
external.MapGitHubAuthEndpoints<AppUser>();
external.MapGoogleAuthEndpoints<AppUser>();
external.MapExternalAccountEndpoints<AppUser>();

MapExternalAccountEndpoints maps one link/{scheme} route pair for each registered provider. The schemes are GitHub and Google.

TaskEndpointNeeds a signed-in userCSRFReAuth
Start a linkGET /auth/external/link/{scheme}?returnUrl=YesNoNo
Finish a linkGET /auth/external/link/{scheme}/callbackYesNoNo
List linked loginsGET /auth/external/loginsYesNoNo
Unlink a loginDELETE /auth/external/logins/{loginProvider}/{providerKey}YesYesYes
  1. While the user is signed in, send the browser to the link route with a top-level navigation:
    window.location.assign('/auth/external/link/GitHub?returnUrl=' + encodeURIComponent('/settings/logins'));
    
  2. The provider sends the user back to the callback. The callback checks that the provider login came from the same signed-in user.
  3. On success, the callback links the login and returns a 302 to returnUrl.

When RequireVerifiedEmail is true (the default), the provider email must be verified. A failed link redirects to ErrorPath?error=...&error_description=.... The codes are provider_mismatch, email_unverified, login_link_failed, and external_login_info_missing.

Linking does not compare the provider email with the account email. The user proves both identities by being signed in and completing the provider sign-in.

List linked logins

Send GET /auth/external/logins. The response is an array of { "loginProvider", "providerKey", "providerDisplayName" }.

  1. Complete step-up. See Require step-up before sensitive actions.
  2. Get a CSRF token from GET /identity/csrfToken.
  3. Send DELETE /auth/external/logins/<loginProvider>/<providerKey> with the RequestVerificationToken header. Take both values from the list response.
  4. Check the response:
    • 204: the login is unlinked.
    • 401: the user has no ReAuth proof.
    • 404: the user has no such login.
    • 400 with title last_signin_method: the login is the last way to sign in. The user must keep a password, a passkey, or another external login.