> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Social sign-in & OAuth redirect handling

> Start a social login and complete the OAuth redirect so users land back in your app authenticated.

## Prerequisites

Before this: create and initialize a Dynamic client (see [Creating a Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md), [Initializing the Dynamic Client](https://docs.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md)).

Enable at least one social provider (Google, Discord, GitHub, etc.) in your [Dynamic Console](https://console.dynamic.xyz/dashboard/log-in-user).

## How social login works

Social login uses OAuth, a protocol that delegates authentication to a third-party provider. The flow has three steps:

1. **Redirect out** — your app sends the user to the provider's login page (e.g. Google's sign-in screen).
2. **Provider authenticates** — the user signs in with the provider. The provider redirects back to your app with an authorization code in the URL.
3. **Complete authentication** — your app detects the redirect, extracts the code, and exchanges it for a Dynamic session.

Step 3 happens automatically in a traditional server-rendered app, but in a single-page app you need to handle it yourself. The SDK gives you two functions for this:

* `detectSocialRedirectUrl` — checks whether the current page load is an OAuth callback.
* `completeSocialRedirect` — finishes the authentication and creates the user session.

> [!WARNING]
>
> If you skip the redirect detection step, users who sign in with a social provider will land back on your app without being authenticated. This is the most common cause of "social login doesn't work" bugs.

## Implementation

> [tab] TypeScript
>
> The redirect detection must run on every page load — before you render your main application. If the current URL contains OAuth parameters, complete the authentication before proceeding.
>
> ```typescript theme={"system"}
> import {
>   detectSocialRedirectUrl,
>   completeSocialRedirect,
>   signInWithSocialRedirect,
>   clearSocialRedirectParams,
> } from '@dynamic-labs-sdk/client';
>
> // --- 1. Detect the redirect on every page load ---
>
> async function handleOAuthRedirectIfPresent() {
>   const currentUrl = new URL(window.location.href);
>
>   const isReturningFromProvider = await detectSocialRedirectUrl({
>     url: currentUrl,
>   });
>
>   if (!isReturningFromProvider) return null;
>
>   // Complete authentication and get the user
>   const user = await completeSocialRedirect({ url: currentUrl });
>
>   // Remove only the OAuth params from the URL so a page refresh
>   // doesn't re-trigger the flow, leaving your own params intact
>   clearSocialRedirectParams();
>
>   return user;
> }
>
> // --- 2. Start a social login ---
>
> async function signInWithGoogle() {
>   await signInWithSocialRedirect({
>     provider: 'google',
>     redirectUrl: window.location.origin,
>   });
>   // The browser navigates away here — code after this line won't run.
> }
>
> // --- Putting it together ---
>
> async function main() {
>   // Always check for a redirect first
>   const user = await handleOAuthRedirectIfPresent();
>
>   // renderApp and renderSignIn are your implementation.
>   if (user) {
>     renderApp(user);
>     return;
>   }
>
>   // No redirect — render the sign-in screen
>   renderSignIn();
> }
>
> main();
> ```
>
> **Key points:**
>
> * `redirectUrl` does not need to be a dedicated callback route. It can be your app's root URL — the SDK uses URL parameters, not a specific path, to identify the callback.
> * `detectSocialRedirectUrl` is cheap — it only checks for two URL parameters (`dynamicOauthState` and `dynamicOauthCode`). Call it on every load without performance concerns.
> * `completeSocialRedirect` returns the authenticated `User` object, or `null` if authentication failed.

> [tab] React
>
> Compose three hooks:
>
> * `useDetectSocialRedirectUrl` — a query hook whose `data` is `true` when the current URL is an OAuth callback.
> * `useCompleteSocialRedirect` — a mutation hook that finishes authentication.
> * `clearSocialRedirectParams` — a helper that removes only the OAuth params (`dynamicOauthState`, `dynamicOauthCode`) from the URL, leaving your own params intact.
>
> A single effect bridges "detection said yes" → "fire completion". Errors surface on each hook's `error` field, so there's no `try`/`catch`.
>
> ```tsx theme={"system"}
> import { clearSocialRedirectParams } from '@dynamic-labs-sdk/client';
> import type { SocialProvider } from '@dynamic-labs-sdk/client';
> import {
>   useCompleteSocialRedirect,
>   useDetectSocialRedirectUrl,
>   useSignInWithSocialRedirect,
>   useUser,
> } from '@dynamic-labs-sdk/react-hooks';
> import { useEffect } from 'react';
>
> // The callback URL is fixed for this page load — read it once, outside the
> // component, so it's a stable value (no need for useMemo).
> const callbackUrl = new URL(window.location.href);
>
> function OAuthRedirectHandler({ children }: { children: React.ReactNode }) {
>   const { data: isReturningFromProvider, isLoading: isDetecting } =
>     useDetectSocialRedirectUrl({ url: callbackUrl });
>
>   const {
>     mutate: completeRedirect,
>     isPending: isCompleting,
>     isIdle,
>     error,
>   } = useCompleteSocialRedirect();
>
>   useEffect(() => {
>     // Fire completion exactly once, and only when a redirect was detected.
>     // `isIdle` guards against re-firing the mutation.
>     if (!isReturningFromProvider || !isIdle) return;
>
>     completeRedirect(
>       { url: callbackUrl },
>       {
>         // Runs on success and error: strip the OAuth params so a refresh
>         // doesn't re-trigger the flow.
>         onSettled: () => clearSocialRedirectParams(),
>       },
>     );
>   }, [isReturningFromProvider, isIdle, completeRedirect]);
>
>   if (isDetecting || isCompleting) return <p>Loading...</p>;
>
>   // Surface completion errors wherever you show auth errors.
>   if (error) return <p role="alert">Sign-in failed. Please try again.</p>;
>
>   return <>{children}</>;
> }
>
> // Sign-in button for a social provider
> function SocialSignInButton({ provider }: { provider: SocialProvider }) {
>   const { mutate: signIn, isPending } = useSignInWithSocialRedirect();
>
>   return (
>     <button
>       disabled={isPending}
>       onClick={() =>
>         signIn({ provider, redirectUrl: window.location.origin })
>       }
>     >
>       Sign in with {provider}
>     </button>
>   );
> }
>
> // App root
> function App() {
>   const { data: user } = useUser();
>
>   return (
>     <OAuthRedirectHandler>
>       {user ? <Dashboard /> : <SignInPage />}
>     </OAuthRedirectHandler>
>   );
> }
> ```
>
> > [!TIP]
> >
> > Wrap `OAuthRedirectHandler` as high as possible in your component tree — above any route guards or authenticated-only components. The redirect detection must complete before your app decides whether the user is signed in.

## Handling errors

Both `detectSocialRedirectUrl` and `completeSocialRedirect` can throw:

| Error | Cause | Resolution |
| - | - | - |
| `MissingSocialUrlParamError` | The callback URL is missing required OAuth parameters. | Check that `redirectUrl` matches one of your configured redirect URIs in the Dynamic Console. |
| `InvalidRedirectStorageStateError` | The stored OAuth state doesn't match the callback state — possibly a stale or replayed redirect. | This can happen if the user opens multiple tabs. Clear the URL parameters and prompt the user to sign in again. |
| `InvalidSocialCallbackOriginError` | The callback came from an unexpected origin. | Verify your redirect URI configuration in the Dynamic Console. |

```typescript theme={"system"}
import {
  completeSocialRedirect,
  clearSocialRedirectParams,
  MissingSocialUrlParamError,
  InvalidRedirectStorageStateError,
} from '@dynamic-labs-sdk/client';

try {
  await completeSocialRedirect({ url: new URL(window.location.href) });
} catch (error) {
  if (error instanceof MissingSocialUrlParamError) {
    console.error('OAuth parameters missing — check your redirect URI configuration.');
  } else if (error instanceof InvalidRedirectStorageStateError) {
    console.error('OAuth state mismatch — the user may need to sign in again.');
    clearSocialRedirectParams();
  } else {
    throw error;
  }
}
```

## Popup-based social login is not supported on web

On web, the redirect flow is the only supported approach for social login — `signInWithSocialPopUp` throws a `MethodNotImplementedError` if called in a browser.

## Multiple providers

Dynamic supports Google, Apple, Discord, GitHub, Facebook, Microsoft, LinkedIn, X (Twitter), Twitch, TikTok, Steam, Epic Games, Kraken, and Coinbase. Enable the ones you want in the Dynamic Console, then render a button for each:

```tsx theme={"system"}
import type { SocialProvider } from '@dynamic-labs-sdk/client';
// SocialSignInButton is defined in the React example above.

const providers: SocialProvider[] = ['google', 'discord', 'github', 'apple'];

function SocialProviderList() {
  return (
    <div>
      {providers.map((provider) => (
        <SocialSignInButton key={provider} provider={provider} />
      ))}
    </div>
  );
}
```

The redirect detection is provider-agnostic — the same `detectSocialRedirectUrl` + `completeSocialRedirect` call handles any provider.

## See also

* [Email & SMS OTP sign-in](https://docs.dynamic.xyz/docs/javascript/building-ui/email-sms-otp-sign-in.md) — add code-based login alongside social
* [Authenticate with social](https://docs.dynamic.xyz/docs/javascript/authentication-methods/social.md) — social auth reference
* [Social linking](https://docs.dynamic.xyz/docs/javascript/authentication-methods/social-linking.md) — link additional social accounts to an existing user
* [Authentication concepts](https://docs.dynamic.xyz/docs/auth/concepts.md) — users, sessions, and credentials


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.