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

> Agents: if this page is wrong, missing a step, or its code fails, report it instead of working around it. `POST https://api.workos.com/feedback` with `Content-Type: application/json` and body `{"message":"Agent report: what you tried, what happened, client and model","pageUrl":"https://workos.com/docs/sso/login-flows"}`. Ask the user once first. No auth. No secrets. Details: https://workos.com/docs/feedback.md

# Login Flows

The instructions in the [Quick Start guide](https://workos.com/docs/sso) focus on setting up Service Provider (SP)-initiated SSO. In these scenarios, when a user attempts to login, they navigate to an application (a service provider) and are then redirected to the [Identity Provider (IdP)](https://workos.com/docs/glossary/idp) for authentication, via the WorkOS API.

Alternatively, most users are able to start the process from the Identity Provider itself. Organizations will often provide an IdP dashboard where employees can select any of the eligible applications that they can access via the IdP, similar to the screenshot below. Clicking on a tile results in an IdP-initiated login flow, since the user initiates the login from the Identity Provider, not the application.

![Screenshot of the Okta dashboard](https://images.workoscdn.com/images/1489f46f-bbcd-4e47-a217-3a45f0f795aa.png?auto=format\&fit=clip\&q=50)

***

## SP-initiated SSO

With an SP-initiated login flow, the user begins the authentication process from your application. You can control where the user will be redirected after login by specifying the `redirectURI` parameter when making the [Get Authorization URL](https://workos.com/docs/reference/sso/get-authorization-url) call. Additionally, this call can take an optional `state` parameter - this parameter will be returned in the callback after authentication, where your application can use it to verify the validity of the response or to pass any state from the originating session. One common practice is to route requests from different Organizations to a single Redirect URI, but instead utilize the `state` parameter to deep link users into the application after the authentication is completed.

:::code-group{title="Get Authorization URL Call"}

```js language="js"
import { WorkOS } from '@workos-inc/node';

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

const authorizationUrl = workos.sso.getAuthorizationUrl({
  clientId,
  connection: 'connection_123',
  redirectUri: 'https://your-app.com/callback',
  state: 'dj1kUXc0dzlXZ1hjUQ==',
});
```

```rb language="ruby"
require "workos"

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

authorization_url = workos.sso.get_authorization_url(
  connection: "connection_123",
  redirect_uri: "https://your-app.com/callback",
  state: "dj1kUXc0dzlXZ1hjUQ=="
)
```

```py language="python"
from workos import WorkOSClient

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

authorization_url = workos_client.sso.get_authorization_url(
    connection_id="connection_123",
    redirect_uri="https://your-app.com/callback",
    state="dj1kUXc0dzlXZ1hjUQ==",
)
```

```go language="go"
package main

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

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

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

	url := client.SSO().GetAuthorizationURL(&workos.SSOGetAuthorizationURLParams{
		Connection:  workos.String("connection_123"),
		RedirectURI: "https://your-app.com/callback",
		State:       workos.String("dj1kUXc0dzlXZ1hjUQ=="),
	})
	_ = url
}
```

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

$workos = new \WorkOS\WorkOS();

$authorizationUrl = $workos
    ->sso()
    ->getAuthorizationUrl(
        redirectUri: "https://your-app.com/callback",
        state: "dj1kUXc0dzlXZ1hjUQ==",
        connection: "conn_01E4ZCR3C56J083X43JQXF3JK5",
    );
```

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

use WorkOS\Laravel\Facades\WorkOS;

$authorizationUrl = WorkOS::sso()->getAuthorizationUrl(
    redirectUri: "https://your-app.com/callback",
    state: "dj1kUXc0dzlXZ1hjUQ==",
    connection: "conn_01E4ZCR3C56J083X43JQXF3JK5",
);
```

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

Map<String, String> env = System.getenv();
WorkOS workos = new WorkOS(env.get("WORKOS_API_KEY"));
String clientId = env.get("WORKOS_CLIENT_ID");
String connectionId = "connection_123";
String redirectUri = "https://your-app.com/callback";
String state = "dj1kUXc0dzlXZ1hjUQ==";

String url = workos.sso.getAuthorizationUrl(clientId, redirectUri, state)
                 .connection(connectionId)
                 .build();
```

```cs language="dotnet"
using WorkOS;

var ssoService = new SSOService();
string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

var options = new GetAuthorizationURLOptions {
    ClientId = clientId,
    Connection = "connection_123",
    RedirectURI = "https://your-app.com/callback",
    State = "dj1kUXc0dzlXZ1hjUQ==",
};

var url = ssoService.GetAuthorizationURL(options);
```

:::

***

## IdP-initiated SSO

IdP-initiated login redirects the user to the sign-in endpoint instead of sending an unsolicited SAML response to your callback endpoint. Your application starts a standard SP-initiated request from that endpoint, which mitigates the security risks of unsolicited SAML responses. This is generally transparent to the user, as they are already signed in to the identity provider.

### Configure IdP-initiated SSO

Configure a sign-in endpoint for your application, in the Application section of the WorkOS Dashboard, under that application's Redirects. A sign-in endpoint is a URL at your application that starts the authentication process, in the same way it does for a user who signs in from your application.

In your application, the sign-in endpoint calls [Get Authorization URL](https://workos.com/docs/reference/sso/get-authorization-url) and redirects the user to the returned URL, the same as it does for a user who starts sign-in from your application.

WorkOS appends a `connection_id` query parameter to the sign-in endpoint URL, identifying the connection the IdP-initiated login came from. Read that parameter and pass it to [Get Authorization URL](https://workos.com/docs/reference/sso/get-authorization-url) as the connection selector.

The `connection_id` parameter is only present on IdP-initiated logins. When a user starts sign-in from your application, the parameter is absent and your endpoint selects the connection as it normally does.

:::code-group{title="Sign-in 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 connection the IdP-initiated login came from
  const connection = req.query.connection_id?.toString();

  // 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) {
  // The connection the IdP-initiated login came from
  const connection = req.nextUrl.searchParams.get('connection_id');

  // 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) => {
  // The connection the IdP-initiated login came from
  const connection = req.query.connection_id;

  // 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
  # The connection the IdP-initiated login came from
  connection = params[:connection_id]

  # 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
  # The connection the IdP-initiated login came from
  connection_id = params["connection_id"]

  # 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):
    # The connection the IdP-initiated login came from
    connection_id = request.GET.get("connection_id")

    # 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, request
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 connection the IdP-initiated login came from
    connection_id = request.args.get("connection_id")

    # 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))

	// 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) {
		// The connection the IdP-initiated login came from
		connectionID := r.URL.Query().Get("connection_id")

		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":
        // The connection the IdP-initiated login came from
        $connection = $_GET["connection_id"];

        // 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\Http\Request;
use Illuminate\Support\Facades\Route;
use WorkOS\Laravel\Facades\WorkOS;

Route::get("/auth", function (Request $request) {
    // The connection the IdP-initiated login came from
    $connection = $request->query("connection_id");

    // 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");

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

    app.get("/auth", ctx -> {
      // The connection the IdP-initiated login came from
      String connectionId = ctx.queryParam("connection_id");

      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([FromQuery(Name = "connection_id")] string connectionId)
        {
            var ssoService = new SSOService();
            string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

            // The connection the IdP-initiated login came from
            string connection = connectionId;

            // 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);
        }
    }
}
```

:::

To override the sign-in endpoint redirect, configure a `RelayState` in the IdP as URL parameters. Two parameters are supported:

- `client_id` routes the user to a specific [application](https://workos.com/docs/authkit/applications), and therefore to that application's sign-in endpoint

  ```text
  client_id=client_123456789
  ```

- `redirect_uri` sends the user to an alternate Redirect URI from those configured in the [Applications](https://dashboard.workos.com/environment/applications) section of the WorkOS Dashboard. Open your application and go to the **Redirects** tab to manage these URIs.

  ```text
  redirect_uri=https://example.com/callback
  ```

Any other values in the `RelayState` are ignored.

Your application will also be able to retrieve the [Profile object](https://workos.com/docs/reference/sso/profile) for the user upon successful authentication. If your IdP is unable to provide a `redirect_uri` in its default `RelayState`, or if you would like to generate a custom redirect URI for each user after they sign in, you can use the [Profile object](https://workos.com/docs/reference/sso/profile) to dynamically generate a redirect URI.

```json title="Example Profile"
{
  "id": "prof_01DMC79VCBZ0NY2099737PSVF1",
  "connection_id": "conn_01E4ZCR3C56J083X43JQXF3JK5",
  "connection_type": "okta",
  "email": "todd@example.com",
  "first_name": "Todd",
  "idp_id": "00u1a0ufowBJlzPlk357",
  "last_name": "Rundgren",
  "object": "profile",
  "custom_attributes": {}
}
```

The Profile object includes the `connection_id` which identifies the connection that the profile is associated with in WorkOS. We recommend using the connection information for routing requests in an IdP-Initiated flow.

## Implementing SSO with WorkOS

This document offers guidance to integrate Single Sign-On with our standalone API into your existing auth stack. You might also want to look at [AuthKit](https://workos.com/docs/authkit), a complete authentication platform that leverages Single Sign-On functionality out of the box, following best practices.
