<!-- llms.txt: https://workos.com/llms.txt -->

# Single Sign-On

## Choose your integration approach

There are two ways to integrate Single Sign-On (SSO) with WorkOS:

### (A) With the standalone SSO API

The standalone API (covered in this document), is a standalone API for integrating into an existing auth stack.

### (B) Using WorkOS AuthKit

[AuthKit](https://workos.com/docs/authkit) is a complete authentication platform which includes SSO out of the box.

## How Single Sign-On works

Single Sign-On is the most frequently asked for requirement by organizations looking to adopt new SaaS applications. SSO enables authentication via an organization's [identity provider (IdP)](https://workos.com/docs/glossary/idp).

This service is compatible with any IdP that supports either the [SAML](https://workos.com/docs/glossary/saml) or [OIDC](https://workos.com/docs/glossary/oidc) protocols. It's modeled to meet the [OAuth 2.0](https://workos.com/docs/glossary/oauth-2-0) framework specification, abstracting away the underlying authentication handshakes between different IdPs.

![Authentication Flow Diagram](https://images.workoscdn.com/images/90b84f08-3363-446a-8610-f7b2bd2ee2ca.png?auto=format\&fit=clip\&q=80)\[border=false]

WorkOS SSO API acts as authentication middleware and intentionally does not handle user database management for your application.

## What you'll build

In this guide, we'll take you from learning about Single Sign-On and POC-ing all the way through to authenticating your first user via the WorkOS SSO API.

## Before getting started

To get the most out of this guide, you'll need:

- A [WorkOS account](https://dashboard.workos.com/)
- A local app to integrate SSO with.

Reference these [example apps](https://workos.com/docs/sso/example-apps) as you follow this guide.

## API object definitions

[Connection](https://workos.com/docs/reference/sso/connection)
: The method by which a group of users (typically in a single organization) sign in to your application.

[Profile](https://workos.com/docs/reference/sso/profile)
: Represents an authenticated user. The Profile object contains information relevant to a user in the form of normalized and raw attributes.

## (1) Add SSO to your app

Let's build the SSO authentication workflow into your app.

### Install the WorkOS SDK

WorkOS offers native SDKs in several popular programming languages. Choose a language below to see instructions in your application's language.

Install the SDK using the command below.

:::code-group{title="Install the WorkOS SDK"}

```bash language="js" title="npm" tab="js-1"
npm install @workos-inc/node
```

```bash language="js" title="Yarn" tab="js-2"
yarn add @workos-inc/node
```

```bash language="ruby" title="Terminal" tab="ruby-5"
gem install workos
```

```rb language="ruby" title="Bundler" tab="ruby-6"
gem "workos"
```

```bash language="python"
pip install workos
```

```bash language="go"
go get github.com/workos/workos-go/v10
```

```bash language="php"
composer require workos/workos-php
```

```bash language="laravel"
composer require workos/workos-php-laravel
```

```xml language="java" title="Maven" tab="java-3"
<dependency>
  <groupId>com.workos</groupId>
  <artifactId>workos</artifactId>
  <version>{version}</version>
</dependency>
```

```groovy language="java" title="Gradle" tab="java-4"
dependencies {
  implementation 'com.workos:workos:VERSION'
}
```

```bash language="dotnet"
nuget install WorkOS.net
```

```elixir language="elixir"
def deps do
  [{:workos, "~> 3.1"}]
end
```

```bash language="rust"
cargo add workos
```

:::

### Set secrets

To make calls to WorkOS, provide the API key and, in some cases, the client ID. Store these values as managed secrets, such as `WORKOS_API_KEY` and `WORKOS_CLIENT_ID`, and pass them to the SDKs either as environment variables or directly in your app's configuration based on your preferences.

```plain title="Environment variables"
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'
```

> The code examples use your staging API keys when [signed in](https://dashboard.workos.com)

### Add an endpoint to initiate SSO

The endpoint to initiate SSO via the WorkOS API is responsible for handing off the rest of the authentication workflow to WorkOS. There are a couple configuration options shown below.

You can use the optional `state` parameter to encode arbitrary information to help restore application state between redirects.

- | Using organization ID

  Use the organization parameter when authenticating a user by their specific organization. This is the preferred parameter for SAML and OIDC connections.

  The example below uses the Test Organization that is available in your staging environment and uses a mock identity provider. It's created to help you test your SSO integration without having to go through the process of setting up an account with a real identity provider.

  :::code-group{title="Authentication Endpoint"}

  ```ts language="js" title="Next.js" tab="js-1"
  import type { NextApiRequest, NextApiResponse } from 'next';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export default (_req: NextApiRequest, res: NextApiResponse) => {
    // Use the Test Organization ID to get started. Replace it with
    // the user's real organization ID when you finish the integration.
    const organization = 'org_test_idp';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      organization,
      redirectUri,
      clientId,
    });

    res.redirect(authorizationUrl);
  };
  ```

  ```ts language="js" title="Next.js (App Router)" tab="js-2"
  import { NextRequest, NextResponse } from 'next/server';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export function GET(_req: NextRequest, _res: NextResponse) {
    // Use the Test Organization ID to get started. Replace it with
    // the user's real organization ID when you finish the integration.
    const organization = 'org_test_idp';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      organization,
      redirectUri,
      clientId,
    });

    return NextResponse.redirect(authorizationUrl);
  }
  ```

  ```js language="js" title="Express" tab="js-3"
  const express = require('express');
  const { WorkOS } = require('@workos-inc/node');

  const app = express();

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  app.get('/auth', (_req, res) => {
    // Use the Test Organization ID to get started. Replace it with
    // the user’s real organization ID when you finish the integration.
    const organization = 'org_test_idp';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      organization,
      redirectUri,
      clientId,
    });

    res.redirect(authorizationUrl);
  });
  ```

  ```rb language="ruby" title="Rails" tab="ruby-4"
  require "workos"

  WORKOS = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  def auth
    # Use the Test Organization ID to get started. Replace it with
    # the user’s real organization ID when you finish the integration.
    organization = "org_test_idp"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = WORKOS.sso.get_authorization_url(
      organization: organization,
      redirect_uri: redirect_uri
    )

    redirect_to authorization_url
  end
  ```

  ```rb language="ruby" title="Sinatra" tab="ruby-5"
  require "sinatra"
  require "workos"

  workos = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  get "/auth" do
    # Use the Test Organization ID to get started. Replace it with
    # the user’s real organization ID when you finish the integration.
    organization_id = "org_test_idp"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = workos.sso.get_authorization_url(
      organization: organization_id,
      redirect_uri: redirect_uri
    )

    redirect authorization_url
  end
  ```

  ```py language="python" title="Django" tab="python-6"
  from django import redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )


  def auth(request):
      # Use the Test Organization ID to get started. Replace it with
      # the user's real organization ID when you finish the integration.
      organization_id = "org_test_idp"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          organization_id=organization_id, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```py language="python" title="Flask" tab="python-7"
  from flask import Flask, redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )

  app = Flask(__name__)


  @app.route("/auth")
  def auth():
      # Use the Test Organization ID to get started. Replace it with
      # the user's real organization ID when you finish the integration.
      organization_id = "org_test_idp"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          organization_id=organization_id, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```go language="go"
  package main

  import (
  	workos "github.com/workos/workos-go/v10"
  	"net/http"
  	"os"
  )

  func main() {
  	apiKey := os.Getenv("WORKOS_API_KEY")
  	clientID := os.Getenv("WORKOS_CLIENT_ID")

  	client := workos.NewClient(apiKey, workos.WithClientID(clientID))

  	// Use the Test Organization ID to get started. Replace it with
  	// the user’s real organization ID when you finish the integration.
  	orgID := "org_test_idp"

  	// The callback URI WorkOS should redirect to after the authentication
  	redirectURI := "https://dashboard.my-app.com"

  	http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
  		url := client.SSO().GetAuthorizationURL(&workos.SSOGetAuthorizationURLParams{
  			Organization: workos.String(orgID),
  			RedirectURI:  redirectURI,
  		})
  		http.Redirect(w, r, url, http.StatusSeeOther)
  	})
  }
  ```

  ```php language="php"
  <?php

  require __DIR__ . "/vendor/autoload.php";

  $workos = new \WorkOS\WorkOS();

  switch (strtok($_SERVER["REQUEST_URI"], "?")) {
      case "/auth":
          // Use the Test Organization ID to get started. Replace it with
          // the user's real organization ID when you finish the integration.
          $organization = "org_test_idp";

          // The callback URI WorkOS should redirect to after the authentication
          $redirectUri = "https://dashboard.my-app.com/";

          $authorizationUrl = $workos
              ->sso()
              ->getAuthorizationUrl(
                  organization: $organization,
                  redirectUri: $redirectUri,
              );

          header("Location: $authorizationUrl", true, 302);
          return true;
  }
  ```

  ```php language="laravel"
  <?php

  use Illuminate\Support\Facades\Route;
  use WorkOS\Laravel\Facades\WorkOS;

  Route::get("/auth", function () {
      // Use the Test Organization ID to get started. Replace it with
      // the user's real organization ID when you finish the integration.
      $organization = "org_test_idp";

      // The callback URI WorkOS should redirect to after the authentication
      $redirectUri = "https://dashboard.my-app.com/";

      $authorizationUrl = WorkOS::sso()->getAuthorizationUrl(
          organization: $organization,
          redirectUri: $redirectUri,
      );

      return redirect($authorizationUrl);
  });
  ```

  ```java language="java"
  import com.workos.WorkOS;
  import io.javalin.Javalin;
  import java.util.Map;

  public class Application {
    public static void main(String[] args) {
      Map<String, String> env = System.getenv();
      Javalin app = Javalin.create().start(7001);
      WorkOS workos = new WorkOS(env.get("WORKOS_API_KEY"));
      String clientId = env.get("WORKOS_CLIENT_ID");

      // Use the Test Organization ID to get started. Replace it with
      // the user's real organization ID when you finish the integration.
      String organization = "org_test_idp";

      // The callback URI WorkOS should redirect to after the authentication
      String redirectUri = "https://dashboard.my-app.com";

      app.get("/auth", ctx -> {
        String url = workos.sso.getAuthorizationUrl(clientId, redirectUri)
                         .organization(organization)
                         .build();

        ctx.redirect(url);
      });
    }
  }
  ```

  ```cs language="dotnet"
  using System;
  using Microsoft.AspNetCore.Mvc;
  using WorkOS;

  namespace MyApplication.Controllers
  {
      public class AuthController : Controller
      {
          [HttpGet("auth")]
          public IActionResult Index()
          {
              var ssoService = new SSOService();
              string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

              // Use the Test Organization ID to get started. Replace it with
              // the user's real organization ID when you finish the integration.
              string organization = "org_test_idp";

              // The callback URI WorkOS should redirect to after the authentication.
              string redirectUri = "https://dashboard.my-app.com";

              var options = new GetAuthorizationURLOptions {
                  ClientId = clientId,
                  Organization = organization,
                  RedirectURI = redirectUri,
              };

              var url = ssoService.GetAuthorizationURL(options);

              return Redirect(url);
          }
      }
  }
  ```

  ```elixir language="elixir"
  client =
    WorkOS.client(
      api_key: "sk_example_123456789",
      client_id: "client_123456789"
    )

  # Use the Test Organization ID to get started. Replace it with the user's
  # real organization ID when you finish the integration.
  organization = "org_test_idp"

  # The callback URI WorkOS should redirect to after authentication.
  redirect_uri = "https://dashboard.my-app.com/"

  # Redirect the browser to this URL.
  WorkOS.SSO.get_authorization_url(client, %{
    organization: organization,
    redirect_uri: redirect_uri
  })
  ```

  ```rust language="rust"
  // workos:manual - target the current Rust SDK request model
  use workos::sso::GetAuthorizationUrlParams;
  use workos::Client;

  fn main() -> Result<(), workos::Error> {
      let client = Client::builder()
          .api_key("sk_example_123456789")
          .client_id("client_123456789")
          .build();

      // Use the Test Organization ID to get started. Replace it with the user's
      // real organization ID when you finish the integration.
      let organization = "org_test_idp";

      // The callback URI WorkOS should redirect to after authentication.
      let redirect_uri = "https://dashboard.my-app.com/";

      let mut params = GetAuthorizationUrlParams::new(redirect_uri);
      params.organization = Some(organization.into());

      // Redirect the browser to this URL.
      let _authorization_url = client.sso().get_authorization_url(params)?;

      Ok(())
  }
  ```

  :::

- | Using connection ID

  You can also use the connection parameter for SAML or OIDC connections when authenticating a user by their connection ID.

  :::code-group{title="Authentication Endpoint"}

  ```ts language="js" title="Next.js" tab="js-1"
  import type { NextApiRequest, NextApiResponse } from 'next';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export default (_req: NextApiRequest, res: NextApiResponse) => {
    // A WorkOS Connection ID
    const connection = 'connection_123';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      connection,
      clientId,
      redirectUri,
    });

    res.redirect(authorizationUrl);
  };
  ```

  ```ts language="js" title="Next.js (App Router)" tab="js-2"
  import { NextRequest, NextResponse } from 'next/server';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export function GET(_req: NextRequest, _res: NextResponse) {
    // A WorkOS Connection ID
    const connection = 'connection_123';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      connection,
      clientId,
      redirectUri,
    });

    return NextResponse.redirect(authorizationUrl);
  }
  ```

  ```js language="js" title="Express" tab="js-3"
  const express = require('express');
  const { WorkOS } = require('@workos-inc/node');

  const app = express();

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  app.get('/auth', (_req, res) => {
    // A WorkOS Connection ID
    const connection = 'connection_123';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      connection,
      clientId,
      redirectUri,
    });

    res.redirect(authorizationUrl);
  });
  ```

  ```rb language="ruby" title="Rails" tab="ruby-4"
  require "workos"

  WORKOS = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  def auth
    # A WorkOS Connection ID
    connection = "connection_123"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = WORKOS.sso.get_authorization_url(
      connection: connection,
      redirect_uri: redirect_uri
    )

    redirect_to authorization_url
  end
  ```

  ```rb language="ruby" title="Sinatra" tab="ruby-5"
  require "sinatra"
  require "workos"

  workos = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  get "/auth" do
    # A WorkOS Connection ID
    connection_id = "connection_123"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = workos.sso.get_authorization_url(
      connection: connection_id,
      redirect_uri: redirect_uri
    )

    redirect authorization_url
  end
  ```

  ```py language="python" title="Django" tab="python-6"
  from django import redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )


  def auth(request):
      # A WorkOS Connection ID
      connection_id = "connection_123"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          connection_id=connection_id, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```py language="python" title="Flask" tab="python-7"
  from flask import Flask, redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )

  app = Flask(__name__)


  @app.route("/auth")
  def auth():
      # A WorkOS Connection ID
      connection_id = "connection_123"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          connection_id=connection_id, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```go language="go"
  package main

  import (
  	workos "github.com/workos/workos-go/v10"
  	"net/http"
  	"os"
  )

  func main() {
  	apiKey := os.Getenv("WORKOS_API_KEY")
  	clientID := os.Getenv("WORKOS_CLIENT_ID")

  	client := workos.NewClient(apiKey, workos.WithClientID(clientID))

  	// A WorkOS Connection ID
  	connectionID := "connection_123"

  	// The callback URI WorkOS should redirect to after the authentication
  	redirectURI := "https://dashboard.my-app.com"

  	http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
  		url := client.SSO().GetAuthorizationURL(&workos.SSOGetAuthorizationURLParams{
  			Connection:  workos.String(connectionID),
  			RedirectURI: redirectURI,
  		})
  		http.Redirect(w, r, url, http.StatusSeeOther)
  	})
  }
  ```

  ```php language="php"
  <?php

  require __DIR__ . "/vendor/autoload.php";

  $workos = new \WorkOS\WorkOS();

  switch (strtok($_SERVER["REQUEST_URI"], "?")) {
      case "/auth":
          // A WorkOS Connection ID
          $connection = "connection_123";

          // The callback URI WorkOS should redirect to after the authentication
          $redirectUri = "https://dashboard.my-app.com/";

          $authorizationUrl = $workos
              ->sso()
              ->getAuthorizationUrl(
                  connection: $connection,
                  redirectUri: $redirectUri,
              );

          header("Location: $authorizationUrl", true, 302);
          return true;
  }
  ```

  ```php language="laravel"
  <?php

  use Illuminate\Support\Facades\Route;
  use WorkOS\Laravel\Facades\WorkOS;

  Route::get("/auth", function () {
      // A WorkOS Connection ID
      $connection = "connection_123";

      // The callback URI WorkOS should redirect to after the authentication
      $redirectUri = "https://dashboard.my-app.com/";

      $authorizationUrl = WorkOS::sso()->getAuthorizationUrl(
          redirectUri: $redirectUri,
          connection: $connection,
      );

      return redirect($authorizationUrl);
  });
  ```

  ```java language="java"
  import com.workos.WorkOS;
  import io.javalin.Javalin;
  import java.util.Map;

  public class Application {
    public static void main(String[] args) {
      Map<String, String> env = System.getenv();
      Javalin app = Javalin.create().start(7001);
      WorkOS workos = new WorkOS(env.get("WORKOS_API_KEY"));
      String clientId = env.get("WORKOS_CLIENT_ID");

      // A WorkOS Connection ID
      String connectionId = "connection_123";

      // The callback URI WorkOS should redirect to after authenticating
      String redirectUri = "https://dashboard.my-app.com";

      app.get("/auth", ctx -> {
        String url = workos.sso.getAuthorizationUrl(clientId, redirectUri)
                         .connection(connectionId)
                         .build();

        ctx.redirect(url);
      });
    }
  }
  ```

  ```cs language="dotnet"
  using System;
  using Microsoft.AspNetCore.Mvc;
  using WorkOS;

  namespace MyApplication.Controllers
  {
      public class AuthController : Controller
      {
          [HttpGet("auth")]
          public IActionResult Index()
          {
              var ssoService = new SSOService();
              string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

              // A WorkOS Connection ID
              string connection = "connection_123";

              // The callback URI WorkOS should redirect to after the authentication
              string redirectUri = "https://dashboard.my-app.com";

              var options = new GetAuthorizationURLOptions {
                  ClientId = clientId,
                  Connection = connection,
                  RedirectURI = redirectUri,
              };

              var url = ssoService.GetAuthorizationURL(options);

              return Redirect(url);
          }
      }
  }
  ```

  ```elixir language="elixir"
  client =
    WorkOS.client(
      api_key: "sk_example_123456789",
      client_id: "client_123456789"
    )

  # A WorkOS Connection ID.
  connection = "connection_123"

  # The callback URI WorkOS should redirect to after authentication.
  redirect_uri = "https://dashboard.my-app.com/"

  # Redirect the browser to this URL.
  WorkOS.SSO.get_authorization_url(client, %{
    connection: connection,
    redirect_uri: redirect_uri
  })
  ```

  ```rust language="rust"
  // workos:manual - target the current Rust SDK request model
  use workos::sso::GetAuthorizationUrlParams;
  use workos::Client;

  fn main() -> Result<(), workos::Error> {
      let client = Client::builder()
          .api_key("sk_example_123456789")
          .client_id("client_123456789")
          .build();

      // A WorkOS Connection ID.
      let connection = "connection_123";

      // The callback URI WorkOS should redirect to after authentication.
      let redirect_uri = "https://dashboard.my-app.com/";

      let mut params = GetAuthorizationUrlParams::new(redirect_uri);
      params.connection = Some(connection.into());

      // Redirect the browser to this URL.
      let _authorization_url = client.sso().get_authorization_url(params)?;

      Ok(())
  }
  ```

  :::

- | Using provider

  The provider parameter is used for OAuth connections which are configured at the environment level.

  > The supported `provider` values are `GoogleOAuth`, `MicrosoftOAuth`, `GitHubOAuth`, and `AppleOAuth`.

  :::code-group{title="Authentication Endpoint"}

  ```ts language="js" title="Next.js" tab="js-1"
  import type { NextApiRequest, NextApiResponse } from 'next';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export default (_req: NextApiRequest, res: NextApiResponse) => {
    // The provider to authenticate with
    const provider = 'GoogleOAuth';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      provider,
      redirectUri,
      clientId,
    });

    res.redirect(authorizationUrl);
  };
  ```

  ```ts language="js" title="Next.js (App Router)" tab="js-2"
  import { NextRequest, NextResponse } from 'next/server';
  import { WorkOS } from '@workos-inc/node';

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  export function GET(_req: NextRequest, _res: NextResponse) {
    // The provider to authenticate with
    const provider = 'GoogleOAuth';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      provider,
      redirectUri,
      clientId,
    });

    return NextResponse.redirect(authorizationUrl);
  }
  ```

  ```js language="js" title="Express" tab="js-3"
  const express = require('express');
  const { WorkOS } = require('@workos-inc/node');

  const app = express();

  const workos = new WorkOS(process.env.WORKOS_API_KEY);
  const clientId = process.env.WORKOS_CLIENT_ID;

  app.get('/auth', (_req, res) => {
    // The provider to authenticate with
    const provider = 'GoogleOAuth';

    // The callback URI WorkOS should redirect to after the authentication
    const redirectUri = 'https://dashboard.my-app.com';

    const authorizationUrl = workos.sso.getAuthorizationUrl({
      provider,
      redirectUri,
      clientId,
    });

    res.redirect(authorizationUrl);
  });
  ```

  ```rb language="ruby" title="Rails" tab="ruby-4"
  require "workos"

  WORKOS = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  def auth
    # The provider to authenticate with
    provider = "GoogleOAuth"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = WORKOS.sso.get_authorization_url(
      provider: provider,
      redirect_uri: redirect_uri
    )

    redirect_to authorization_url
  end
  ```

  ```rb language="ruby" title="Sinatra" tab="ruby-5"
  require "sinatra"
  require "workos"

  workos = WorkOS::Client.new(
    api_key: ENV["WORKOS_API_KEY"],
    client_id: ENV["WORKOS_CLIENT_ID"]
  )

  get "/auth" do
    # The provider to authenticate with
    provider = "GoogleOAuth"

    # The callback URI WorkOS should redirect to after the authentication
    redirect_uri = "https://dashboard.my-app.com"

    authorization_url = workos.sso.get_authorization_url(
      provider: provider,
      redirect_uri: redirect_uri
    )

    redirect authorization_url
  end
  ```

  ```py language="python" title="Django" tab="python-6"
  from django import redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )


  def auth(request):
      # The provider to authenticate with
      provider = "GoogleOAuth"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          provider=provider, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```py language="python" title="Flask" tab="python-7"
  from flask import Flask, redirect
  from workos import WorkOSClient

  workos_client = WorkOSClient(
      api_key="sk_example_123456789", client_id="client_123456789"
  )

  app = Flask(__name__)


  @app.route("/auth")
  def auth():
      # The provider to authenticate with
      provider = "GoogleOAuth"

      # The callback URI WorkOS should redirect to after the authentication
      redirect_uri = "https://dashboard.my-app.com"

      authorization_url = workos_client.sso.get_authorization_url(
          provider=provider, redirect_uri=redirect_uri
      )

      return redirect(authorization_url)
  ```

  ```go language="go"
  package main

  import (
  	workos "github.com/workos/workos-go/v10"
  	"net/http"
  	"os"
  )

  func main() {
  	apiKey := os.Getenv("WORKOS_API_KEY")
  	clientID := os.Getenv("WORKOS_CLIENT_ID")

  	client := workos.NewClient(apiKey, workos.WithClientID(clientID))

  	// The provider to authenticate with
  	provider := "GoogleOAuth"

  	// The callback URI WorkOS should redirect to after the authentication
  	redirectURI := "https://dashboard.my-app.com"

  	http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
  		ssoProvider := workos.SSOProvider(provider)
  		url := client.SSO().GetAuthorizationURL(&workos.SSOGetAuthorizationURLParams{
  			Provider:    &ssoProvider,
  			RedirectURI: redirectURI,
  		})
  		http.Redirect(w, r, url, http.StatusSeeOther)
  	})
  }
  ```

  ```php language="php"
  <?php

  require __DIR__ . "/vendor/autoload.php";

  $workos = new \WorkOS\WorkOS();

  switch (strtok($_SERVER["REQUEST_URI"], "?")) {
      case "/auth":
          // The provider to authenticate with
          $provider = \WorkOS\Resource\SSOProvider::GoogleOAuth;

          // The callback URI WorkOS should redirect to after the authentication
          $redirectUri = "https://dashboard.my-app.com/";

          $authorizationUrl = $workos
              ->sso()
              ->getAuthorizationUrl(
                  provider: $provider,
                  redirectUri: $redirectUri,
              );

          header("Location: $authorizationUrl", true, 302);
          return true;
  }
  ```

  ```php language="laravel"
  <?php

  use Illuminate\Support\Facades\Route;
  use WorkOS\Laravel\Facades\WorkOS;
  use WorkOS\Resource\SSOProvider;

  Route::get("/auth", function () {
      // The provider to authenticate with
      $provider = SSOProvider::GoogleOAuth;

      // The callback URI WorkOS should redirect to after the authentication
      $redirectUri = "https://dashboard.my-app.com/";

      $authorizationUrl = WorkOS::sso()->getAuthorizationUrl(
          provider: $provider,
          redirectUri: $redirectUri,
      );

      return redirect($authorizationUrl);
  });
  ```

  ```java language="java"
  import com.workos.WorkOS;
  import io.javalin.Javalin;
  import java.util.Map;

  public class Application {
    public static void main(String[] args) {
      Map<String, String> env = System.getenv();
      Javalin app = Javalin.create().start(7001);
      WorkOS workos = new WorkOS(env.get("WORKOS_API_KEY"));
      String clientId = env.get("WORKOS_CLIENT_ID");

      // The provider to authenticate with
      String provider = "GoogleOAuth";

      // The callback URI WorkOS should redirect to after the authentication
      String redirectUri = "https://dashboard.my-app.com";

      app.get("/auth", ctx -> {
        String url = workos.sso.getAuthorizationUrl(clientId, redirectUri)
                         .provider(provider)
                         .build();

        ctx.redirect(url);
      });
    }
  }
  ```

  ```cs language="dotnet"
  using System;
  using Microsoft.AspNetCore.Mvc;
  using WorkOS;

  namespace MyApplication.Controllers
  {
      public class AuthController : Controller
      {
          [HttpGet("auth")]
          public IActionResult Index()
          {
              var ssoService = new SSOService();
              string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

              // The provider to authenticate with
              string provider = "GoogleOAuth";

              // The callback URI WorkOS should redirect to after the authentication
              string redirectUri = "https://dashboard.my-app.com";

              var options = new GetAuthorizationURLOptions {
                  ClientId = clientId,
                  Provider = provider,
                  RedirectURI = redirectUri,
              };

              var url = ssoService.GetAuthorizationURL(options);

              return Redirect(url);
          }
      }
  }
  ```

  ```elixir language="elixir"
  client =
    WorkOS.client(
      api_key: "sk_example_123456789",
      client_id: "client_123456789"
    )

  # The provider to authenticate with.
  provider = "GoogleOAuth"

  # The callback URI WorkOS should redirect to after authentication.
  redirect_uri = "https://dashboard.my-app.com/"

  # Redirect the browser to this URL.
  WorkOS.SSO.get_authorization_url(client, %{
    provider: provider,
    redirect_uri: redirect_uri
  })
  ```

  ```rust language="rust"
  // workos:manual - target the current Rust SDK request model
  use workos::sso::GetAuthorizationUrlParams;
  use workos::{Client, SSOProvider};

  fn main() -> Result<(), workos::Error> {
      let client = Client::builder()
          .api_key("sk_example_123456789")
          .client_id("client_123456789")
          .build();

      // The callback URI WorkOS should redirect to after authentication.
      let redirect_uri = "https://dashboard.my-app.com/";

      let mut params = GetAuthorizationUrlParams::new(redirect_uri);
      params.provider = Some(SSOProvider::GoogleOAuth);

      // Redirect the browser to this URL.
      let _authorization_url = client.sso().get_authorization_url(params)?;

      Ok(())
  }
  ```

  :::

If there is an issue generating an authorization URL, WorkOS will return the redirect URI as is. Read the [API Reference](https://workos.com/docs/reference/sso/get-authorization-url) for more details.

### Add a callback endpoint

Next, let's add the redirect endpoint which will handle the callback from WorkOS after a user has authenticated with their identity provider. This endpoint should exchange the authorization code returned by WorkOS with the authenticated user's profile. The authorization code is valid for 10 minutes.

:::code-group{title="Callback Endpoint"}

```ts language="js" title="Next.js" tab="js-1"
import type { NextApiRequest, NextApiResponse } from 'next';
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID;

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { code } = req.query;

  const { profile } = await workos.sso.getProfileAndToken({
    code,
    clientId,
  });

  // Use the Test Organization ID to get started. Replace it with
  // the user's real organization ID when you finish the integration.
  const organization = 'org_test_idp';

  // Validate that this profile belongs to the organization used for authentication
  if (profile.organizationId !== organization) {
    return res.status(401).send({
      message: 'Unauthorized',
    });
  }

  // Use the information in `profile` for further business logic.

  res.redirect('/');
};
```

```ts language="js" title="Next.js (App Router)" tab="js-2"
import { NextRequest, NextResponse } from 'next/server';
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID;

export async function GET(req: NextRequest, _res: NextResponse) {
  const searchParams = req.nextUrl.searchParams;
  const code = searchParams.get('code');

  const { profile } = await workos.sso.getProfileAndToken({
    code,
    clientId,
  });

  // Use the Test Organization ID to get started. Replace it with
  // the user's real organization ID when you finish the integration.
  const organization = 'org_test_idp';

  // Validate that this profile belongs to the organization used for authentication
  if (profile.organizationId !== organization) {
    return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
  }

  // Use the information in `profile` for further business logic.

  return NextResponse.redirect('/');
}
```

```js language="js" title="Express" tab="js-3"
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID;

app.get('/callback', async (req, res) => {
  const { code } = req.query;

  const { profile } = await workos.sso.getProfileAndToken({
    code,
    clientId,
  });

  // Use the Test Organization ID to get started. Replace it with
  // the user’s real organization ID when you finish the integration.
  const organization = 'org_test_idp';

  // Validate that this profile belongs to the organization used for authentication
  if (profile.organizationId !== organization) {
    return res.status(401).send({
      message: 'Unauthorized',
    });
  }

  // Use the information in `profile` for further business logic.

  res.redirect('/');
});
```

```rb language="ruby" title="Rails" tab="ruby-4"
require "workos"

WORKOS = WorkOS::Client.new(
  api_key: ENV["WORKOS_API_KEY"],
  client_id: ENV["WORKOS_CLIENT_ID"]
)

def callback
  profile_and_token = WORKOS.sso.get_profile_and_token(
    code: params["code"]
  )

  profile = profile_and_token.profile

  # Use the Test Organization ID to get started. Replace it with
  # the user’s real organization ID when you finish the integration.
  organization = "org_test_idp"

  # Validate that this profile belongs to the organization used for authentication
  if profile.organization_id != organization
    render json: {error: "Unauthorized", status: 401}.to_json
  end

  sign_in_and_redirect profile

  # Use the information in `profile` for further business logic.
end
```

```rb language="ruby" title="Sinatra" tab="ruby-5"
require "sinatra"
require "workos"

workos = WorkOS::Client.new(
  api_key: ENV["WORKOS_API_KEY"],
  client_id: ENV["WORKOS_CLIENT_ID"]
)

get "/callback" do
  profile_and_token = workos.sso.get_profile_and_token(
    code: params["code"]
  )

  profile = profile_and_token.profile

  # Use the Test Organization ID to get started. Replace it with
  # the user’s real organization ID when you finish the integration.
  organization = "org_test_idp"

  # Validate that this profile belongs to the organization used for authentication
  if profile.organization_id != organization
    halt 401, "Unauthorized"
  end

  redirect "/"

  # Use the information in `profile` for further business logic.
end
```

```py language="python" title="Django" tab="python-6"
from django import redirect
from django.core.exceptions import PermissionDenied
from workos import WorkOSClient

workos_client = WorkOSClient(
    api_key="sk_example_123456789", client_id="client_123456789"
)


def callback(request):
    code = request.GET["code"]
    profile_and_token = workos_client.sso.get_profile_and_token(code)

    profile = profile_and_token.profile

    # Use the Test Organization ID to get started. Replace it with
    # the user's real organization ID when you finish the integration.
    organization = "org_test_idp"

    # Validate that this profile belongs to the organization used for authentication
    if profile.organization_id != organization:
        raise PermissionDenied

    # Use the information in `profile` for further business logic.

    return redirect("/")
```

```py language="python" title="Flask" tab="python-7"
from flask import Flask, redirect, request
from workos import WorkOSClient

workos_client = WorkOSClient(
    api_key="sk_example_123456789", client_id="client_123456789"
)

app = Flask(__name__)


@app.route("/callback")
def callback():
    code = request.args.get("code")
    profile_and_token = workos_client.sso.get_profile_and_token(code)

    profile = profile_and_token.profile

    # Use the Test Organization ID to get started. Replace it with
    # the user's real organization ID when you finish the integration.
    organization = "org_test_idp"

    # Validate that this profile belongs to the organization used for authentication
    if profile.organization_id != organization:
        return "Unauthorized", 401

    # Use the information in `profile` for further business logic.

    return redirect("/")
```

```go language="go"
// workos:manual - preserve callback validation guidance
package main

import (
	"context"

	"github.com/workos/workos-go/v10"
)

func main() {
	client := workos.NewClient(
		"sk_example_123456789",
		workos.WithClientID("client_123456789"),
	)

	profileAndToken, err := client.SSO().GetProfileAndToken(context.Background(), &workos.SSOGetProfileAndTokenParams{
		Code: "authorization_code_value",
	})
	if err != nil {
		// Handle the error according to your application's needs.
		return
	}

	organizationID := "org_test_idp"
	if profileAndToken.Profile == nil ||
		profileAndToken.Profile.OrganizationID == nil ||
		*profileAndToken.Profile.OrganizationID != organizationID {
		// Return an unauthorized response.
		return
	}

	// Use the information in `profileAndToken.Profile` for further business logic.
}
```

```php language="php"
<?php

// workos:manual - preserve callback validation guidance
use WorkOS\WorkOS;

$workos = new WorkOS(
    apiKey: "sk_example_123456789",
    clientId: "client_123456789",
);

$profileAndToken = $workos
    ->sso()
    ->getProfileAndToken(code: "authorization_code_value");

$organizationId = "org_test_idp";
if ($profileAndToken->profile->organizationId !== $organizationId) {
    throw new \RuntimeException("Unauthorized organization");
}

// Use the information in `$profileAndToken->profile` for further business logic.
```

```php language="laravel"
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use WorkOS\Laravel\Facades\WorkOS;

Route::get("/callback", function (Request $request) {
    $code = $request->input("code");
    $profileAndToken = WorkOS::sso()->getProfileAndToken($code);

    $profile = $profileAndToken->profile;

    // Use the Test Organization ID to get started. Replace it with
    // the user's real organization ID when you finish the integration.
    $organization = "org_test_idp";

    // Validate that this profile belongs to the organization used for authentication
    if ($profile->organizationId != $organization) {
        return Response::json(
            [
                "message" => "Unauthorized",
            ],
            401,
        );
    }

    // Use the information in `profile` for further business logic.

    return redirect("/");
});
```

```java language="java"
// workos:manual - preserve callback validation guidance
import com.workos.WorkOS;
import com.workos.models.SSOTokenResponse;

WorkOS workos =
    WorkOS.builder().apiKey("sk_example_123456789").clientId("client_123456789").build();

SSOTokenResponse profileAndToken =
    workos.getSso().getProfileAndToken("authorization_code_value");

String organizationId = "org_test_idp";
if (!organizationId.equals(profileAndToken.getProfile().getOrganizationId())) {
  throw new SecurityException("Unauthorized organization");
}

// Use the information in `profileAndToken.getProfile()` for further business logic.
```

```cs language="dotnet"
// workos:manual - preserve callback validation guidance
using WorkOS;

var client = new WorkOSClient(new WorkOSOptions {
    ApiKey = "sk_example_123456789",
    ClientId = "client_123456789",
});

var profileAndToken = await client.SSO.GetProfileAndTokenAsync(new SSOGetProfileAndTokenOptions {
    Code = "authorization_code_value",
});

var organizationId = "org_test_idp";
if (profileAndToken.Profile.OrganizationId != organizationId)
{
    throw new UnauthorizedAccessException("Unauthorized organization");
}

// Use the information in `profileAndToken.Profile` for further business logic.
```

```elixir language="elixir"
# workos:manual - preserve callback validation guidance
client =
  WorkOS.client(
    api_key: "sk_example_123456789",
    client_id: "client_123456789"
  )

{:ok, profile_and_token} =
  WorkOS.SSO.get_profile_and_token(client, %{
    code: "authorization_code_value"
  })

organization_id = "org_test_idp"

if profile_and_token.profile.organization_id != organization_id do
  raise "Unauthorized organization"
end

# Use profile_and_token.profile for further business logic.
```

```rust language="rust"
// workos:manual - preserve callback validation guidance
use workos::sso::GetProfileAndTokenParams;
use workos::{Client, TokenQuery};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder()
        .api_key("sk_example_123456789")
        .client_id("client_123456789")
        .build();

    let profile_and_token = client
        .sso()
        .get_profile_and_token(GetProfileAndTokenParams::new(
            "authorization_code_value",
            TokenQuery {
                client_id: "client_123456789".into(),
                client_secret: "sk_example_123456789".into(),
                code: "authorization_code_value".into(),
                grant_type: "authorization_code".into(),
            },
        ))
        .await?;

    let organization_id = "org_test_idp";
    if profile_and_token.profile.organization_id.as_deref() != Some(organization_id) {
        return Err(std::io::Error::other("Unauthorized organization").into());
    }

    // Use the information in `profile_and_token.profile` for further business logic.

    Ok(())
}
```

:::

When adding your callback endpoint, it is important to always validate the returned profile's organization ID. It's unsafe to validate using email domains as organizations might allow email addresses from outside their corporate domain (e.g. for guest users).

***

## (2) Configure a redirect URI

In the [Applications](https://dashboard.workos.com/environment/applications) section of the WorkOS Dashboard, open your application and go to the **Redirects** tab to configure allowed redirect URIs. Add your callback endpoint from the previous section.

Multi-tenant apps will typically have a single redirect URI specified. You can set multiple redirect URIs for single-tenant apps. You'll need to be sure to specify which redirect URI to use in the WorkOS client call to fetch the authorization URL.

> More information about wildcard characters support can be found in the [Redirect URIs](https://workos.com/docs/sso/redirect-uris/wildcard-characters) guide.

![Redirects in the Dashboard](https://images.workoscdn.com/images/195dbff3-adbf-4010-b07c-ffc73ceeca68.png?auto=format\&fit=clip\&q=90)

### Identity provider-initiated SSO

Normally, the default redirect URI you configure for your application is going to be used for all identity provider-initiated SSO sessions. This is because the WorkOS client is not used to initiate the authentication flow.

However, your customer can specify a separate redirect URI to be used for all their IdP-initiated sessions as a `RelayState` parameter in the SAML settings on their side.

Learn more about configuring IdP-initiated SSO in the [Login Flows](https://workos.com/docs/sso/login-flows/idp-initiated-sso/configure-idp-initiated-sso) guide.

***

## (3) Test end-to-end

If you followed this guide, you used the Test Organization available in your staging environment to initiate SSO. With that, you can already test your integration end-to-end.

![Test SSO WorkOS Dashboard](https://images.workoscdn.com/images/ebc5063a-70a3-4f79-92c3-a36a1963ae12.png?auto=format\&fit=clip\&q=80)

Head to the *Test SSO* page in the [WorkOS Dashboard](https://dashboard.workos.com/) to get started with testing common login flows, or read on about that in detail in the next guide.
