OAuth Single Sign-On (SSO) on WordPress using Discord

Create a Discord Application
Head to https://discord.com/developers/applications and create a new application.

Add a Redirect URI
In the OAuth2 settings we need to add a redirect URI.
This is the where the user gets redirected once

In our wordpress theme/child-teme functions.php we add this code to register the redirect URI we gave the Discord Application.
function discord_oauth_endpoint() {
add_rewrite_rule( 'discord-auth/?$', 'index.php?discord=1', 'top' );
}
add_action( 'init', 'discord_oauth_endpoint' );
To be sure this rewrite rule is actually being applied, we refresh our wordpress permalinks by going to <your domain>/wp-admin/options-permalink.php and just clicking on “save changes”.
Generate the OAuth2 URL

We need to generate the URL that users will use to authenticate.
Select the “identify” and “email” scopes, we need the first to authenticate the user, and the second to register an account on wordpress with their email.
If you need your app to do more here’s the description of each scope.
Copy the generated URL and use it for your login button in your theme.
When someone navigates to this URL, they will be prompted to authorize your application for the requested scopes. On acceptance, they will be redirected to your redirect_uri
, which will contain an additional querystring parameter, code
.
Copy Client ID and Secret
The client ID can be found in the General Information page or the same OAuth2 page

To copy the Client Secret you need to actually first click on “Reset Secret”

Don’t worry, this app isn’t being used anywhere at the moment, so click on “Yes, do it!” and save this code safely, if you lose it you need to reset it again.

The Authentication Process
Listening for the code Discord sends
We need to listen for the code that Discord sends us.
This code listens for redirects, if the referrer isn’t discord, or the code parameter is empty, it does nothing, otherwise it calls our Discord callback function that we are going to define at the end.
function handle_oauth_process() {
$referrer = wp_get_raw_referer();
if ( 'https://discord.com/' !== $referrer ) {
return;
}
if ( empty( $_GET['code'] ) ) {
return;
}
disc_callback();
}
add_action( 'template_redirect', 'handle_oauth_process' );
Get the Access Token
Discord sends with the redirect uri, a code parameter. We need to send this code back to the Discord API with our client id and client secret
function disc_auth(string $code) {
$payload = [
'code' => $code,
'client_id' => '<your client id>',
'client_secret' => '<your client secret>',
'grant_type' => 'authorization_code',
'redirect_uri' => '<your domain>/discord-auth',
'scope' => 'identity%20email',
];
$result = wp_remote_post(
'https://discord.com/api/oauth2/token',
[
'headers' => [
'Content-type' => 'application/x-www-form-urlencoded',
],
'body' => $payload,
]
);
$response = json_decode( wp_remote_retrieve_body( $result ), true );
return $response;
}
The response looks like this:
{
"access_token": "6qrZcUqja7812RVdnEKjpzOL4CvHBFG",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": "D43f5y0ahjqew82jZ4NViEr2YafMKhue",
"scope": "identify%20email"
}
We are going to use this token to login or register the user on our WordPress website.
Retreive the Discord User data
To register a new user in wordpress we use the wp_insert_user() function.
We need to pass some parameters to this function to be able to create a unique account. We’ll use the token we just got and the Discord API to retreive the data we need.
function disc_get_user_data(string $access_token) {
$user_request = wp_remote_get(
"https://discord.com/api/users/@me",
[
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
],
]
);
$user_data = json_decode( wp_remote_retrieve_body( $user_request ), true );
return $user_data;
}
The user data we retreived looks like this:
{
"id": "80351110224678912",
"username": "Nelly",
"discriminator": "1337",
"avatar": "8342729096ea3675442027381ff50dfe",
"verified": true,
"email": "[email protected]",
"flags": 64,
"banner": "06c16474723fe537c283b8efa61a30c8",
"accent_color": 16711680,
"premium_type": 1,
"public_flags": 64
}
Register the WordPress User
We got the user data from Discord, let’s finally create the user in WordPress
function disc_create_user( array $disc_user ) {
$args = [
'user_login' => sanitize_text_field( $disc_user['id']),
'nickname' => sanitize_text_field( $disc_user['username'] ),
'display_name' => sanitize_text_field( $disc_user['username'] ),
'user_email' => sanitize_email( $disc_user['email'] ),
'user_pass' => wp_generate_password(),
'role' => 'subscriber',
];
$inserted_user_id = wp_insert_user( $args );
if ( is_wp_error( $inserted_user_id ) ) {
return $inserted_user_id;
}
$meta = create_disc_user_meta_array( $disc_user );
update_user_meta( $inserted_user_id, 'discord_meta', $meta );
return get_userdata( $inserted_user_id );
}
In WordPress the user’s username must be unique, Discord usernames are now unique, but can be changed up to 2 times a week. We don’t want users to create a new WordPress account every time they update their discord email or change their username, so we are going to user their unique ID as the WordPress username.
We also want users to have a readable name though, so we are going to set their display_name
and nickname
with their discord username.
It is nice if we store all of the user’s Discord data, so in case they change their discord data, we can check on our database and update the relevant data.
function create_disc_user_meta_array( array $discord_user ): array {
$meta = [
'id' => sanitize_text_field( $discord_user['id'] ?? '' ),
'avatar' => sanitize_text_field( $discord_user['avatar'] ?? '' ),
'discriminator' => sanitize_text_field( $discord_user['discriminator'] ?? '' ),
'public_flags' => sanitize_text_field( $discord_user['public_flags'] ?? '' ),
'flags' => sanitize_text_field( $discord_user['flags'] ?? '' ),
'banner' => sanitize_text_field( $discord_user['banner'] ?? '' ),
'accent_color' => sanitize_text_field( $discord_user['accent_color'] ?? '' ),
'locale' => sanitize_text_field( $discord_user['locale'] ?? '' ),
'mfa_enabled' => boolval( $discord_user['mfa_enabled'] ?? false ),
'premium_type' => sanitize_text_field( $discord_user['premium_type'] ?? '' ),
'verified' => boolval( $discord_user['verified'] ?? '' ),
];
$meta['hash'] = md5( json_encode( $meta ) );
return $meta;
}
We store the hash of the data too, this way on login we can just compare the hash to see if something changed.
The Callback Function
We built all the functions we need, but we are missing the callback function that gets called when the user gets redirected after the authorization prompt, so let’s put together everything we wrote.
function disc_callback() {
$response = disc_auth($_GET['code']);
$disc_user = disc_get_user_data($response['access_token']);
if ( empty( $disc_user ) ) {
return; // Do nothing for now.
}
$user = get_user_by( 'login', $disc_user['id'] );
if ( ! $user ) {
$user = disc_create_user( $disc_user );
} else {
$new_meta = create_disc_user_meta_array( $disc_user );
$old_meta = get_user_meta( $user->ID, 'discord_meta', true );
if ( empty( $old_meta['hash'] ) || $new_meta['hash'] !== $old_meta['hash'] ) {
update_user_meta( $user->ID, 'discord_meta', $new_meta );
}
}
wp_clear_auth_cookie();
wp_set_current_user( $user->ID );
wp_set_auth_cookie( $user->ID );
wp_safe_redirect( '<your domain>' );
exit();
}
TL;DR
add_action( 'init', 'discord_oauth_endpoint' );
function discord_oauth_endpoint() {
add_rewrite_rule( 'discord-auth/?$', 'index.php?discord=1', 'top' );
}
add_action( 'template_redirect', 'handle_oauth_process' );
function handle_oauth_process() {
$referrer = wp_get_raw_referer();
if ( 'https://discord.com/' !== $referrer ) {
return;
}
if ( empty( $_GET['code'] ) ) {
return;
}
disc_callback();
}
function disc_callback() {
$response = disc_auth($_GET['code']);
$disc_user = disc_get_user_data($response['access_token']);
if ( empty( $disc_user ) ) {
return;
}
$user = get_user_by( 'login', $disc_user['id'] );
if ( ! $user ) {
$user = disc_create_user( $disc_user );
} else {
$new_meta = create_disc_user_meta_array( $disc_user );
$old_meta = get_user_meta( $user->ID, 'discord_meta', true );
if ( empty( $old_meta['hash'] ) || $new_meta['hash'] !== $old_meta['hash'] ) {
update_user_meta( $user->ID, 'discord_meta', $new_meta );
}
}
wp_clear_auth_cookie();
wp_set_current_user( $user->ID );
wp_set_auth_cookie( $user->ID );
wp_safe_redirect( '<your domain>' );
exit();
}
function disc_auth(string $code) {
$payload = [
'code' => $code,
'client_id' => '<your client id>',
'client_secret' => '<your client secret>',
'grant_type' => 'authorization_code',
'redirect_uri' => '<your domain>/discord-auth',
'scope' => 'identity%20email',
];
$result = wp_remote_post(
'https://discord.com/api/oauth2/token',
[
'headers' => [
'Content-type' => 'application/x-www-form-urlencoded',
],
'body' => $payload,
]
);
$response = json_decode( wp_remote_retrieve_body( $result ), true );
return $response;
}
function disc_get_user_data(string $access_token) {
$user_request = wp_remote_get(
"https://discord.com/api/users/@me",
[
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
],
]
);
$user_data = json_decode( wp_remote_retrieve_body( $user_request ), true );
return $user_data;
}
function disc_create_user( array $disc_user ) {
$args = [
'user_login' => sanitize_text_field( $disc_user['id']),
'nickname' => sanitize_text_field( $disc_user['username'] ),
'display_name' => sanitize_text_field( $disc_user['username'] ),
'user_email' => sanitize_email( $disc_user['email'] ),
'user_pass' => wp_generate_password(),
'role' => 'subscriber',
];
$inserted_user_id = wp_insert_user( $args );
if ( is_wp_error( $inserted_user_id ) ) {
return $inserted_user_id;
}
$meta = create_disc_user_meta_array( $disc_user );
update_user_meta( $inserted_user_id, 'discord_meta', $meta );
return get_userdata( $inserted_user_id );
}
function create_disc_user_meta_array( array $discord_user ): array {
$meta = [
'id' => sanitize_text_field( $discord_user['id'] ?? '' ),
'avatar' => sanitize_text_field( $discord_user['avatar'] ?? '' ),
'discriminator' => sanitize_text_field( $discord_user['discriminator'] ?? '' ),
'public_flags' => sanitize_text_field( $discord_user['public_flags'] ?? '' ),
'flags' => sanitize_text_field( $discord_user['flags'] ?? '' ),
'banner' => sanitize_text_field( $discord_user['banner'] ?? '' ),
'accent_color' => sanitize_text_field( $discord_user['accent_color'] ?? '' ),
'locale' => sanitize_text_field( $discord_user['locale'] ?? '' ),
'mfa_enabled' => boolval( $discord_user['mfa_enabled'] ?? false ),
'premium_type' => sanitize_text_field( $discord_user['premium_type'] ?? '' ),
'verified' => boolval( $discord_user['verified'] ?? '' ),
];
$meta['hash'] = md5( json_encode( $meta ) );
return $meta;
}