Docs

SAML connections

Caution

This guide is for users who want to build a custom user interface using the Clerk API. To authenticate users with SAML connections using a prebuilt UI, you should use Clerk's Account Portal pages or prebuilt components.

Before you start

You must configure your application instance through the Clerk Dashboard for the SAML connection(s) that you want to use. Visit the appropriate SAML guide for your platform to learn how to configure your instance.

Create the sign-up and sign-in flow

When using SAML, the sign-in and sign-up are equivalent. A successful SAML flow consists of the following steps:

  1. Start the SAML flow by calling SignIn.authenticateWithRedirect(params) or SignUp.authenticateWithRedirect(params). Note that both of these methods require a redirectUrl param, which is the URL that the browser will be redirected to once the user authenticates with the OAuth provider.
  2. Create a route at the URL that the redirectUrl param points to. It's recommended to name this route /sso-callback. This route should either call the Clerk.handleRedirectCallback() method or render the prebuilt <AuthenticateWithRedirectCallback/> component.

The following example shows two files:

  1. The sign-in page where the user can start the SAML flow.
  2. The SSO callback page where the SAML flow is completed.
app/sign-in/[[...sign-in]]/page.tsx
'use client';

import * as React from 'react';
import { useSignIn, useSignUp } from '@clerk/nextjs';

export default function OauthSignIn() {
  const { signIn } = useSignIn();
  const { signUp } = useSignUp();

  if (!signIn || !signUp) return null;

  const signInWith = (e: React.FormEvent) => {
    e.preventDefault();

    const email = (e.target as HTMLFormElement).email.value;

    signIn
      .authenticateWithRedirect({
        identifier: email,
        strategy: 'saml',
        redirectUrl: '/sso-callback',
        redirectUrlComplete: '/',
      })
      .then((res) => {
        console.log(res);
      })
      .catch((err: any) => {
        console.log(err.errors);
        console.error(err, null, 2);
      });
  };

  // Render a button for each supported SAML provider
  // you want to add to your app
  return (
    <form onSubmit={(e) => signInWith(e)}>
      <input type="email" name="email" placeholder="Enter email address" />
      <button>Sign in with SAML</button>
    </form>
  );
}
app/sign-up/sso-callback/page.tsx
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs';

export default function SSOCallback() {
  console.log('testing');
  // Handle the redirect flow by rendering the
  // prebuilt AuthenticateWithRedirectCallback component.
  // This is the final step in the custom SAML flow.
  return <AuthenticateWithRedirectCallback />;
}

SAML account transfer flows

In some cases, a user may be trying to sign in with a SAML account, but they don't have an account in your application yet. Or a user may be trying to sign up with a SAML account, but they already have an account in your application. In these cases, Clerk provides "account transfers" which allow you to forward the user's information to the appropriate flow.

The following example shows how to handle these cases in your sign-in flow. The same logic can be applied to the sign-up flow; simply change the signIn.AuthenticateWithRedirect() to signUp.authenticateWithRedirect().

app/sign-in/[[...sign-in]]/page.tsx
'use client';

import * as React from 'react';
import { useSignIn, useSignUp } from '@clerk/nextjs';

export default function OauthSignIn() {
  const { signIn } = useSignIn();
  const { signUp, setActive } = useSignUp();

  if (!signIn || !signUp) return null;

  async function handleSignIn(e: React.FormEvent) {
    if (!signIn || !signUp) return null;

    // If the user has an account in your application, but does not yet
    // have a SAML account connected to it, you can transfer the SAML
    // account to the existing user account.
    const userExistsButNeedsToSignIn =
      signUp.verifications.externalAccount.status === 'transferable' &&
      signUp.verifications.externalAccount.error?.code ===
        'external_account_exists';

    if (userExistsButNeedsToSignIn) {
      const res = await signIn.create({ transfer: true });

      if (res.status === 'complete') {
        setActive({
          session: res.createdSessionId,
        });
      }
    }

    // If the user has a SAML account but does not yet
    // have an account in your app, you can create an account
    // for them using the SAML information.
    const userNeedsToBeCreated =
      signIn.firstFactorVerification.status === 'transferable';

    if (userNeedsToBeCreated) {
      const res = await signUp.create({
        transfer: true,
      });

      if (res.status === 'complete') {
        setActive({
          session: res.createdSessionId,
        });
      }
    } else {
      // If the user has an account in your application
      // and has an SAML account connected to it, you can sign them in.
      signInWith(e);
    }
  }

  const signInWith = (e: React.FormEvent) => {
    e.preventDefault();

    const email = (e.target as HTMLFormElement).email.value;

    signIn
      .authenticateWithRedirect({
        identifier: email,
        strategy: 'saml',
        redirectUrl: '/sso-callback',
        redirectUrlComplete: '/',
      })
      .then((res) => {
        console.log(res);
      })
      .catch((err: any) => {
        console.log(err.errors);
        console.error(err, null, 2);
      });
  };

  // Render a button for each supported OAuth provider
  // you want to add to your app
  return (
    <form onSubmit={(e) => handleSignIn(e)}>
      <input type="email" name="email" placeholder="Enter email address" />
      <button>Sign in with SAML</button>
    </form>
  );
}

Feedback

What did you think of this content?