# Magic Link

> **Deprecated:** Magic Link is not recommended due to issues with email clients or other security software visiting the links and invalidating them. Instead, use [Magic Auth](https://workos.com/docs/authkit/magic-auth), which provides a more secure passwordless authentication method.

## Introduction

Managing password authentication entails significant overhead for engineering teams and opens up many risks for breaches. Magic Link from WorkOS gives you the ability to build a secure passwordless authentication flow utilizing email. Send users a one-time-use link and let them sign in with a single click.

> Magic Link sets you up to handle Single Sign-On via the [WorkOS SSO API](https://workos.com/docs/sso). The redirect URI / profile retrieval process is identical between Magic Link and SSO.

![Diagram showing Magic Link flow which emphasizes that WorkOS does not manage the session.](https://images.workoscdn.com/docs/guides/magic-link/v1/magic-link-diagram.png?auto=format\&fit=clip\&q=50)\[border=false]

Magic Link is not a session management solution – instead it is an authentication solution. You as the developer have the ability to manage user sessions as you see fit. This means you determine how long a session is valid for, as well as how to store and invalidate a session.

Magic Links are single use, meaning that they will immediately expire after they are first clicked. If end users have security checks included with their email provider that test all embedded URLs, it can cause the Magic Links to expire before they reach the user’s inbox. To work around this, we recommend that end users add these Magic Link emails to an allowlist in order to bypass these security checks.

## What you’ll build

In this guide, we’ll take you from learning about passwordless authentication all the way through to building production-ready passwordless authentication flows with the WorkOS Magic Link API.

This guide will show you how to:

1. Add Magic Link to your app
2. Configure a redirect URI
3. Create a new Magic Link Connection

## Before getting started

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

- A [WorkOS account](https://dashboard.workos.com/)

## API object definitions

[Passwordless Session](https://workos.com/docs/reference/magic-link/passwordless-session)
: Represents a passwordless authentication session.

[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 Magic Link to 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.

### 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'
```

### Add a callback endpoint

Let’s first add the redirect endpoint which will handle the callback from WorkOS after a user has authenticated via a magic link. This endpoint should exchange the authorization code (valid for 10 minutes) returned by WorkOS with the authenticated user’s Profile.

#### Callback Endpoint

```go
package main

import (
	"net/http"
	"os"

	"github.com/workos/workos-go/v3/pkg/sso"
)

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

	sso.Configure(apiKey, clientID)

	http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
		opts := sso.GetProfileAndTokenOpts{
			Code: r.URL.Query().Get("code"),
		}

		profileAndToken, err := sso.GetProfileAndToken(r.Context(), opts)

		if err != nil {
			// Handle the error...
		}

		profile := profileAndToken.Profile

		// The user's Organization ID
		orgID := "org_123"

		// Validate that this profile belongs to the organization used for authentication
		if profile.OrganizationId != orgID {
			http.Error(w, "Unauthorized", http.StatusForbidden)
		}

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

		http.Redirect(w, r, "/", http.StatusSeeOther)
	})
}
```

```php
<?php

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

WorkOS\WorkOS::setApiKey("sk_example_123456789");
WorkOS\WorkOS::setClientId("client_123456789");

$sso = new WorkOS\SSO();

switch (strtok($_SERVER["REQUEST_URI"], "?")) {
    case "/callback":
        $code = $_GET["code"];
        $profileAndToken = $sso->getProfileAndToken($code);

        $profile = $profileAndToken->profile;

        // The user's organization ID
        $organization = "org_123";

        // Validate that this profile belongs to the organization used for authentication
        if ($profile->organization_id != $organization) {
            http_response_code(401);
            echo json_encode(["message" => "Unauthorized"]);
            return;
        }

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

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

```php
<?php

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

WorkOS\WorkOS::setApiKey("sk_example_123456789");
WorkOS\WorkOS::setClientId("client_123456789");

$sso = new WorkOS\SSO();

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

    $profile = $profileAndToken->profile;

    // The user's organization ID
    $organization = "org_123";

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

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

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

```java
import com.workos.WorkOS;
import com.workos.sso.models.Profile;
import com.workos.sso.models.ProfileAndToken;
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");

    app.get("/callback", ctx -> {
      String code = ctx.queryParam("code");
      ProfileAndToken profileAndToken = workos.sso.getProfileAndToken(code, clientId);

      Profile profile = profileAndToken.profile;

      // The user's Organization ID
      String organization = "org_123";

      // Validate that this profile belongs to the organization used for authentication
      if (profile.organizationId != organization) {
        ctx.json("Unauthorized").status(401)
      }

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

      ctx.redirect("/");
    });
  }
}
```

```cs
using System;
using Microsoft.AspNetCore.Mvc;
using WorkOS;

namespace MyApplication.Controllers
{
    public class CallbackController : Controller
    {
        [HttpGet("callback")]
        public async Task<IActionResult> Index([FromQuery(Name = "code")] string code)
        {
            var ssoService = new SSOService();
            string clientId = Environment.GetEnvironmentVariable("WORKOS_CLIENT_ID");

            var options = new GetProfileAndTokenOptions {
                ClientId = clientId,
                Code = code,
            };

            var profileAndToken = await ssoService.GetProfileAndToken(options);

            var profile = profileAndToken.Profile;

            // The user's Organization ID
            string organization = "org_123";

            // Validate that this profile belongs to the organization used for authentication
            if (profile.OrganizationId != organization)
            {
                var resp = new HttpResponseMessage(
                    HttpStatusCode.Unauthorized) { Content = new StringContent("Unauthorized") };
                throw new HttpResponseException(resp);
            }

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

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

### Create a Passwordless Session and email the user

Create a Passwordless Session to generate an authentication link for a user. An authentication link is valid for 15 minutes and can be sent via the WorkOS API or using your own email service.

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

You can use the optional `redirect_uri` parameter to override the default [Redirect URI](https://workos.com/docs/glossary/redirect-uri) set in the dashboard.

> A Magic Link Connection will be automatically created if there isn’t already one for the domain specified when creating a new Passwordless Session.

- | WorkOS email

  Use the WorkOS API to send the authentication link to the user. The email sent will be WorkOS branded.

  #### Create a Passwordless Session

  ```go
  package main

  import (
  	"net/http"
  	"os"

  	"github.com/workos/workos-go/v3/pkg/passwordless"
  )

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

  	passwordless.SetAPIKey(apiKey)

  	http.HandleFunc("/passwordless-auth", func(w http.ResponseWriter, r *http.Request) {
  		r.ParseForm()

  		// Email of the user to authenticate
  		email := r.Form["email"][0]

  		session, err := passwordless.CreateSession(r.Context(), passwordless.CreateSessionOpts{
  			Email: email,
  			Type:  passwordless.MagicLink,
  		})

  		if err != nil {
  			// Handle the error...
  		}

  		// Send an email to the user via WorkOS with the link to authenticate
  		err = passwordless.SendSession(r.Context(), passwordless.SendSessionOpts{
  			SessionID: session.ID,
  		})

  		if err != nil {
  			// Handle the error...
  		}

  		// Finally, redirect to a "Check your email" page
  	})
  }
  ```

  ```php
  <?php

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

  $passwordless = new WorkOS\Passwordless();

  switch (strtok($_SERVER["REQUEST_URI"], "?")) {
      case "/passwordless-auth":
          // Email of the user to authenticate
          $email = $_POST["email"];

          // Generate a session for passwordless
          $session = $passwordless->createSession(
              email: $email,
              type: "MagicLink"
          );

          // Send an email to the user via WorkOS with the link to authenticate
          $passwordless->sendSession($session);

      // Finally, redirect to a "Check your email" page
  }
  ```

  ```php
  <?php

  use Illuminate\Support\Facades\Route;

  $passwordless = new WorkOS\Passwordless();

  Route::get("/auth", function () {
      // Email of the user to authenticate
      $email = $request->input("email");

      // Generate a session for passwordless
      $session = $passwordless->createSession(email: $email, type: "MagicLink");

      // Send an email to the user via WorkOS with the link to authenticate
      $passwordless->sendSession($session);

      // Finally, redirect to a "Check your email" page
  });
  ```

  ```java
  import com.workos.WorkOS;
  import com.workos.passwordless.PasswordlessApi.CreateSessionOptions;
  import com.workos.passwordless.models.PasswordlessSession;
  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");

      app.get("/passwordless-auth", ctx -> {
        // Email of the user to authenticate
        String email = ctx.formParam("email");

        CreateSessionOptions options = CreateSessionOptions.builder().email(email).build();
        PasswordlessSession session = workos.passwordless.createSession(options);

        // Send an email to the user via WorkOS with the link to authenticate
        workos.passwordless.sendSession(session.id);

        // Finally, redirect to a "Check your email" page
        ctx.redirect("/check-email");
      });
    }
  }
  ```

  ```cs
  using System;
  using Microsoft.AspNetCore.Mvc;
  using WorkOS;

  namespace MyApplication.Controllers
  {
      public class PasswordlessAuthController : Controller
      {
          [HttpPost("passwordless-auth")]
          public async Task<IActionResult> Index()
          {
              var passwordlessService = new PasswordlessService();

              // Email of the user to authenticate
              string email = "email@example.com";

              var options = new CreatePasswordlessSessionOptions {
                  Email = email,
                  Type = PasswordlessSessionType.MagicLink,
              };

              var session = await passwordlessService.CreateSession(options);

              // Send an email to the user via WorkOS with the link to authenticate
              await passwordlessService.SendSession(session.Id);

              // Finally, redirect to a "Check your email" page
          }
      }
  }
  ```

- | Custom email

  Use your own email service and custom branded email template to send the authentication link to the user.

  #### Create a Passwordless Session

  ```go
  package main

  import (
  	"net/http"
  	"os"

  	"github.com/workos/workos-go/v3/pkg/passwordless"
  )

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

  	passwordless.SetAPIKey(apiKey)

  	http.HandleFunc("/passwordless-auth", func(w http.ResponseWriter, r *http.Request) {
  		r.ParseForm()

  		// Email of the user to authenticate
  		email := r.Form["email"][0]

  		session, err := passwordless.CreateSession(r.Context(), passwordless.CreateSessionOpts{
  			Email: email,
  			Type:  passwordless.MagicLink,
  		})

  		if err != nil {
  			// Handle the error...
  		}

  		// Send a custom email using your own service
  		email.Send(r.Context(), email.SendOpts{
  			Email:   email,
  			Message: session.Link,
  		})

  		// Finally, redirect to a "Check your email" page
  	})
  }
  ```

  ```php
  <?php

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

  $passwordless = new WorkOS\Passwordless();

  switch (strtok($_SERVER["REQUEST_URI"], "?")) {
      case "/passwordless-auth":
          // Email of the user to authenticate
          $email = $_POST["email"];

          // Generate a session for passwordless
          $session = $passwordless->createSession(
              email: $email,
              type: "MagicLink"
          );

          // Send a custom email using your own service
          send($email, $session->link);

      // Finally, redirect to a "Check your email" page
  }
  ```

  ```php
  <?php

  use Illuminate\Support\Facades\Route;

  $passwordless = new WorkOS\Passwordless();

  Route::get("/auth", function () {
      // Email of the user to authenticate
      $email = $request->input("email");

      // Generate a session for passwordless
      $session = $passwordless->createSession(email: $email, type: "MagicLink");

      // Send a custom email using your own service
      send($email, $session->link);

      // Finally, redirect to a "Check your email" page
  });
  ```

  ```java
  import com.workos.WorkOS;
  import com.workos.passwordless.PasswordlessApi.CreateSessionOptions;
  import com.workos.passwordless.models.PasswordlessSession;
  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");

      app.get("/passwordless-auth", ctx -> {
        // Email of the user to authenticate
        String email = ctx.formParam("email");

        CreateSessionOptions options = CreateSessionOptions.builder().email(email).build();
        PasswordlessSession session = workos.passwordless.createSession(options);

        // Send a custom email using your own service
        sendCustomEmail(email, session.link);

        // Finally, redirect to a "Check your email" page
        ctx.redirect("/check-email");
      });
    }
  }
  ```

  ```cs
  using System;
  using Microsoft.AspNetCore.Mvc;
  using WorkOS;

  namespace MyApplication.Controllers
  {
      public class PasswordlessAuthController : Controller
      {
          [HttpPost("passwordless-auth")]
          public async Task<IActionResult> Index()
          {
              var passwordlessService = new PasswordlessService();

              // Email of the user to authenticate
              string email = "email@example.com";

              var options = new CreatePasswordlessSessionOptions {
                  Email = email,
                  Type = PasswordlessSessionType.MagicLink,
              };

              var session = await passwordlessService.CreateSession(options);

              // Send a custom email using your own service
              var emailOptions = new SendEmailOptions {
                  Email = email,
                  Message = session.Link,
              };

              await this.emailService.SendSession(emailOptions);

              // Finally, redirect to a "Check your email" page
          }
      }
  }
  ```

***

## (2) Configure a redirect URI

You should set a redirect URI (i.e. the callback endpoint from [Add a callback endpoint](https://workos.com/docs/magic-link/1-add-magic-link-to-your-app/add-a-callback-endpoint)) via the Configuration page in the WorkOS Dashboard – be sure not to include wildcard subdomains or query parameters.

![A screenshot showing where to add a callback in the WorkOS Dashboard.](https://images.workoscdn.com/images/43ffd916-890b-4f1f-8767-0b70113288d9.png?auto=format\&fit=clip\&q=50)
