Implementing Magic Link Authentication: A Complete Guide
Magic Links work by integrating a single-use, time-limited Token in the URL of an access link sent by e-mail. This token is associated with the user's account and is verified by Cryptr when the link is accessed, before redirecting the authorized user to your application. With this integration, you can embed Magic Link authentication in your code in just two API requests.
- Quickstart
- 15 min
In this guide, we’ll walk you through implementing Magic Link authentication in your application.
Before starting
Create your free Cryptr account now, and you will have the three elements needed for this guide.
- API Key: You will receive a
client_id
and aclient_secret
. Read our guide to learn how to authenticate with these elements to use the Cryptr API. - Organization: You will create your first organization, which could be your customer or even yourself for the first test. Learn more about Organization.
- Redirection: A redirect, also called a
redirect_uri
, is the address your user will be sent to after successful authentication.
The Magic Link authentication method is activated by default for all new Organizations. You can manage its activation from your Cryptr Dashboard.
Activate Magic Links for an Organization
You can activate Magic Links directly from the page of one of your Organizations on your Cryptr dashboard.
What we’re going to build together
There are three steps to completing a Magic Link Challenge process for an Organization
end user (such as an employee of your customer).
- From your BackEnd, you need to request a MagicLinkChallenge with the desired
redirect_uri
. This is the address of your application where you want Cryptr to redirect the user after authentication. This API call is protected by your Cryptr API key, so you must never make it from your FrontEnd. - The MagicLinkChallenge includes a URL containing a token that allows Cryptr to authenticate the end user. This URL is a one-time use, for a limited time only.
- After successful Magic Link authentication, the user is redirected to the
redirect_uri
, with a query params authorization code-namedcode
. This allows you to retrieve the final Json Web Tokens (JWT).
1. Request a Magic Link challenge
To request a Magic Link challenge, you’ll need
- The user’s e-mail address
- The
redirect_uri
: this is the address of your application (URL) where you want Cryptr to redirect the user after authentication.
To use Cryptr’s authentication strategies, you must first specify the redirections to be authorized (redirect_uri
) from your Cryptr dashboard. If you have not specified a redirection in your Challenge request, the default redirect will be used. Cryptr encourages you to create your first redirection for your development environment (sandbox) when you create your account.
Method 1: Emails sent by Cryptr
- cURL
- Java
- JavaScript
- PHP - Guzzle
- Python
- Ruby
- C#
curl -X POST '${cryptr_service_url}/api/v2/magic-link-challenge' \
-d user_email="john@misapret.com" \
-d redirect_uri="https//example-of_url.com/welcome-back-user"
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "user_email=john@misapret.com&redirect_uri=https//example-of_url.com/welcome-back-user");
Request request = new Request.Builder()
.url("${cryptr_service_url}/api/v2/magic-link-challenge")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Authorization", "Bearer ...")
.build();
Response response = client.newCall(request).execute();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", "Bearer ...");
const urlencoded = new URLSearchParams();
urlencoded.append("user_email", "john@misapret.com");
urlencoded.append("redirect_uri", "https//example-of_url.com/welcome-back-user");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow"
};
fetch("${cryptr_service_url}/api/v2/magic-link-challenge", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
<?php
$client = new Client();
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Bearer ...'
];
$options = [
'form_params' => [
'user_email' => 'john@misapret.com',
'redirect_uri' => 'https//example-of_url.com/welcome-back-user'
]];
$request = new Request('POST', '${cryptr_service_url}/api/v2/magic-link-challenge', $headers);
$res = $client->sendAsync($request, $options)->wait();
echo $res->getBody();
import requests
url = "${cryptr_service_url}/api/v2/magic-link-challenge"
payload = 'user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ...'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
require "uri"
require "net/http"
url = URI("${cryptr_service_url}/api/v2/magic-link-challenge")
http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/x-www-form-urlencoded"
request["Authorization"] = "Bearer ..."
request.body = "user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user"
response = http.request(request)
puts response.read_body
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "${cryptr_service_url}/api/v2/magic-link-challenge");
request.Headers.Add("Authorization", "Bearer ...");
var collection = new List<KeyValuePair<string, string>>();
collection.Add(new("user_email", "john@misapret.com"));
collection.Add(new("redirect_uri", "https//example-of_url.com/welcome-back-user"));
var content = new FormUrlEncodedContent(collection);
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
This request will send an e-mail to the user. The user must click on the link to authenticate before being redirected to the desired redirect_uri
.
Method 2: Send your own emails
- cURL
- Java
- JavaScript
- PHP - Guzzle
- Python
- Ruby
- C#
curl -X POST '${cryptr_service_url}/api/v2/magic-link-challenge' \
-d user_email="john@misapret.com" \
-d redirect_uri="https//example-of_url.com/welcome-back-user" \
-d send_email="false"
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "user_email=john@misapret.com&redirect_uri=https//example-of_url.com/welcome-back-user&send_email=false");
Request request = new Request.Builder()
.url("${cryptr_service_url}/api/v2/magic-link-challenge")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Authorization", "Bearer ...")
.build();
Response response = client.newCall(request).execute();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", "Bearer ...");
const urlencoded = new URLSearchParams();
urlencoded.append("user_email", "john@misapret.com");
urlencoded.append("redirect_uri", "https//example-of_url.com/welcome-back-user");
urlencoded.append("send_email", "false");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow"
};
fetch("${cryptr_service_url}/api/v2/magic-link-challenge", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
<?php
$client = new Client();
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Bearer ...'
];
$options = [
'form_params' => [
'user_email' => 'john@misapret.com',
'redirect_uri' => 'https//example-of_url.com/welcome-back-user',
'send_email' => 'false'
]];
$request = new Request('POST', '${cryptr_service_url}/api/v2/magic-link-challenge', $headers);
$res = $client->sendAsync($request, $options)->wait();
echo $res->getBody();
import requests
url = "${cryptr_service_url}/api/v2/magic-link-challenge"
payload = 'user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user&send_email=false'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ...'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
require "uri"
require "net/http"
url = URI("${cryptr_service_url}/api/v2/magic-link-challenge")
http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/x-www-form-urlencoded"
request["Authorization"] = "Bearer ..."
request.body = "user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user&send_email=false"
response = http.request(request)
puts response.read_body
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "${cryptr_service_url}/api/v2/magic-link-challenge");
request.Headers.Add("Authorization", "Bearer ...");
var collection = new List<KeyValuePair<string, string>>();
collection.Add(new("user_email", "john@misapret.com"));
collection.Add(new("redirect_uri", "https//example-of_url.com/welcome-back-user"));
collection.Add(new("send_email", "false"));
var content = new FormUrlEncodedContent(collection);
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
This request will provide you with a link to send by e-mail to the user. As before, the user must click on the link to authenticate before being redirected to the desired redirect_uri
.
You can also target the appropriate Magic Link connection using the organization’s domain. This is useful if you have several organizations using the same e-mail domains.
Request the Magic Link challenge using an organization’s domain.
- cURL
- Java
- JavaScript
- PHP - Guzzle
- Python
- Ruby
- C#
curl -X POST '${cryptr_service_url}/api/v2/magic-link-challenge' \
-d user_email="john@misapret.com" \
-d org_domain="misapret" \
-d redirect_uri="https//example-of_url.com/welcome-back-user"
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "user_email=john@misapret.com&redirect_uri=https//example-of_url.com/welcome-back-user&org_domain=misapret");
Request request = new Request.Builder()
.url("${cryptr_service_url}/api/v2/magic-link-challenge")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Authorization", "Bearer ...")
.build();
Response response = client.newCall(request).execute();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", "Bearer ...");
const urlencoded = new URLSearchParams();
urlencoded.append("user_email", "john@misapret.com");
urlencoded.append("redirect_uri", "https//example-of_url.com/welcome-back-user");
urlencoded.append("org_domain", "misapret");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow"
};
fetch("${cryptr_service_url}/api/v2/magic-link-challenge", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
<?php
$client = new Client();
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'Bearer ...'
];
$options = [
'form_params' => [
'user_email' => 'john@misapret.com',
'redirect_uri' => 'https//example-of_url.com/welcome-back-user',
'org_domain' => 'misapret'
]];
$request = new Request('POST', '${cryptr_service_url}/api/v2/magic-link-challenge', $headers);
$res = $client->sendAsync($request, $options)->wait();
echo $res->getBody();
import requests
url = "${cryptr_service_url}/api/v2/magic-link-challenge"
payload = 'user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user&org_domain=misapret'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ...'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
require "uri"
require "net/http"
url = URI("${cryptr_service_url}/api/v2/magic-link-challenge")
http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/x-www-form-urlencoded"
request["Authorization"] = "Bearer ..."
request.body = "user_email=john@misapret.com&redirect_uri=https%2F%2Fexample-of_url.com%2Fwelcome-back-user&org_domain=misapret"
response = http.request(request)
puts response.read_body
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "${cryptr_service_url}/api/v2/magic-link-challenge");
request.Headers.Add("Authorization", "Bearer ...");
var collection = new List<KeyValuePair<string, string>>();
collection.Add(new("user_email", "john@misapret.com"));
collection.Add(new("redirect_uri", "https//example-of_url.com/welcome-back-user"));
collection.Add(new("org_domain", "misapret"));
var content = new FormUrlEncodedContent(collection);
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
2. Get tokens after successful authentication
Once the user has successfully authenticated via their Magic Link, they will be redirected to the redirect_uri
provided beforehand. Cryptr will then supply a code
authorization code via query params to retrieve the final tokens: the access_token
and the id_token
. The latter contains the user data retrieved during authentication.
- cURL
- Java
- JavaScript
- PHP - Guzzle
- Python
- Ruby
- C#
# ... your user finishes their Magic Link authentication,
# In your app, the user is redirected to your service,
# via the "redirect_uri" provided when you created the challenge (or the default one),
# with the query parameter "code" that we need to fetch the tokens.
curl -X POST '${cryptr_service_url}/oauth/token' \
-d code={code} \
-d grant_type="authorization_code"
# if result.success is true, then
## 1. you get the result.access_token,
## 2. and result.id_token, which contains signed user data.
# else
## your user is unauthorized
# end
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "code=your_code&grant_type=authorization_code");
Request request = new Request.Builder()
.url("${cryptr_service_url}/oauth/token")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
Response response = client.newCall(request).execute();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
const urlencoded = new URLSearchParams();
urlencoded.append("code", "your_code");
urlencoded.append("grant_type", "authorization_code");
const requestOptions = {
method: "POST",
headers: myHeaders,
body: urlencoded,
redirect: "follow"
};
fetch("${cryptr_service_url}/oauth/token", requestOptions)
.then((response) => response.text())
.then((result) => console.log(result))
.catch((error) => console.error(error));
<?php
$client = new Client();
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded'
];
$options = [
'form_params' => [
'code' => 'your_code',
'grant_type' => 'authorization_code'
]];
$request = new Request('POST', '${cryptr_service_url}/oauth/token', $headers);
$res = $client->sendAsync($request, $options)->wait();
echo $res->getBody();
import requests
url = "${cryptr_service_url}/oauth/token"
payload = 'code=your_code&grant_type=authorization_code'
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
require "uri"
require "net/http"
url = URI("${cryptr_service_url}/oauth/token")
http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Content-Type"] = "application/x-www-form-urlencoded"
request.body = "code=your_code&grant_type=authorization_code"
response = http.request(request)
puts response.read_body
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "${cryptr_service_url}/oauth/token");
var collection = new List<KeyValuePair<string, string>>();
collection.Add(new("code", "your_code"));
collection.Add(new("grant_type", "authorization_code"));
var content = new FormUrlEncodedContent(collection);
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
We will respond to this request with the user’s Json Web Token (JWT), an access_token
and an id_token
. The latter contains the user’s identity data.
What's next
To verify tokens and ensure data trust, you can use our guide: How to validate a JWT
You can also consult our API Reference to perform these actions via API Rest.