Skip to main content
Documentation - Discord Docs

Search documentation

Type to search this documentation.

On this pageOverview

Account Linking from Your Game

This guide explains how to authenticate users with their existing Discord accounts via OAuth2, enabling seamless login and access to Discord features.

If a player does not have a Discord account, you can use the SDK to create a provisional account instead so that they can still access your game's features.

For the account linking integration flow we recommend — provisional account first, and Discord-linked account second — see Recommended Integration Path below.

Before you begin, make sure you have:

  • Read the Core Concepts guide to understand:
    • OAuth2 authentication flow
    • Discord application setup
    • SDK initialization
  • Set up your development environment with:
    • Discord application created in the Developer Portal
    • Discord Social SDK downloaded and configured
    • Basic SDK integration working (initialization and connection)

If you haven't completed these prerequisites, we recommend first following the Getting Started guide.


OAuth2 is the standard authentication flow that allows users to sign in using their Discord account. The process follows these steps:

  1. Request authorization: Your game sends an authentication request to Discord.
  2. User Approval: The user approves the request, granting access to your application.
  3. Receive Authorization Code: After approval, Discord redirects the user to your app with an authorization code.
  4. Exchange for Tokens: The authorization code is exchanged for:
    • Access Token, which is valid for ~7 days
    • Refresh Token, used to obtain a new access token
  • If the Discord client has overlay support (Windows only), the OAuth2 login modal appears in your game instead of opening a browser.
  • The SDK automatically handles redirects, simplifying the authentication flow.
  • Some security measures, such as CSRF protection, are built-in, but you should always follow best practices to secure your app.

For OAuth2 to work correctly, you must register the correct redirect URIs for your app in the Discord Developer Portal.

Platform Redirect URI
Desktop http://127.0.0.1/callback
Mobile discord-APP_ID:/authorize/callback (replace APP_ID with your Discord application ID)

The SDK provides helper methods to simplify OAuth2 login.

Use the Client::Authorize method to initiate authorization and allow the user to approve access.

One of the required arguments to Client::Authorize is scopes — the set of permissions your game is requesting from the user. The Discord Social SDK provides two helper methods that cover the most common use cases:

Helper Method Scopes Requested Features Enabled
Client::GetDefaultPresenceScopes openid sdk.social_layer_presence Account linking, friends list, rich presence
Client::GetDefaultCommunicationScopes openid sdk.social_layer All of the above, plus lobbies, voice chat, direct messaging, and linked channels

Start with Client::GetDefaultPresenceScopes unless you know you need the communication features. You can always add more scopes later as your integration expands. See the OAuth2 Scopes guide for full details.

If you are using Client::GetToken in Step 4, you will need to specify a "code challenge" and "code verifier" in your requests. We'll spare you the boring details of how that works (woo… crypto), as we've made a simple function to create these for you, Client::CreateAuthorizationCodeVerifier, which you can use to generate the code challenge and verifier.

C++
// Create a code verifier and challenge if using GetToken
auto codeVerifier = client->CreateAuthorizationCodeVerifier();
discordpp::AuthorizationArgs args{};
args.SetClientId(YOUR_DISCORD_APPLICATION_ID);
args.SetScopes(discordpp::Client::GetDefaultPresenceScopes());
args.SetCodeChallenge(codeVerifier.Challenge());

client->Authorize(args, [client, codeVerifier](discordpp::ClientResult result, std::string code, std::string redirectUri) {
  if (!result.Successful()) {
    std::cerr << "❌ Authorization Error: " << result.Error() << std::endl;
  } else {
    std::cout << "✅ Authorization successful! Next step: exchange code for an access token \n";
  }
});

After calling Client::Authorize, the SDK will open a browser window, Discord client, or an in-game overlay to prompt the user to approve the request.

Once the user approves the request from Step 2, Discord will redirect the user back to your app with an authorization code that you can use to exchange for an access token.

Step 4: Exchanging the Authorization Code for an Access Token

Section titled “Step 4: Exchanging the Authorization Code for an Access Token”

If your application uses a backend server and does not have Public Client enabled, you can manually exchange the authorization code for an access token using the Discord API.

Python
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'

def exchange_code(code, redirect_uri, code_verifier):
    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': redirect_uri,
        'code_verifier': code_verifier
    }
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    r = requests.post(f'{API_ENDPOINT}/oauth2/token', data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
    r.raise_for_status()
    return r.json()
JSON
{
  "access_token": "<access token>",
  "token_type": "Bearer",
  "expires_in": 604800,
  "refresh_token": "<refresh token>",
  "scope": "sdk.social_layer"
}

If your app does not have a backend server, enable Public Client in the Discord Developer Portal and use Client::GetToken to automatically exchange the authorization code for a token.

We will also need the code verifier used to generate the code challenge in Step 1.

C++
client->GetToken(YOUR_DISCORD_APPLICATION_ID, code, codeVerifier.Verifier(), redirectUri,
  [client](discordpp::ClientResult result,
    std::string accessToken,
    std::string refreshToken,
    discordpp::AuthorizationTokenType tokenType,
    int32_t expiresIn,
    std::string scope) {
    std::cout << "🔓 Access token received! Establishing connection...\n";
    // Next step: Update the token in the client and connect to Discord
  });

Once you've received your access token, you'll want to set the token in the SDK. You can use Client::UpdateToken to do that. At this point, you're authorized and ready to go! You'll want to store the player's access token and refresh tokens somewhere.

Please note that the access_token values do expire. You'll need to use the refresh_token to refresh the player's access token.

C++
client->UpdateToken(discordpp::AuthorizationTokenType::Bearer, ACCESS_TOKEN_VALUE, [client](discordpp::ClientResult result) {
  client->Connect();
);

Access tokens expire after 7 days, requiring refresh tokens to get a new one.

If you're handling authentication on your server, send an API request to refresh the token.

Python
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'

def refresh_token(refresh_token):
    data = {
        'grant_type': 'refresh_token',
        'refresh_token': refresh_token
    }
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    r = requests.post(f'{API_ENDPOINT}/oauth2/token', data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
    r.raise_for_status()
    return r.json()

The easiest way to refresh tokens is using the SDK's Client::RefreshToken method.

C++
client->RefreshToken(
      YOUR_DISCORD_APPLICATION_ID, GetRefreshToken(),
      [client](discordpp::ClientResult result, std::string accessToken,
               std::string refreshToken,
               discordpp::AuthorizationTokenType tokenType, int32_t expiresIn,
               std::string scope) {
        if (!result.Successful()) {
          std::cout << "❌ Error refreshing token: " << result.Error()
                    << std::endl;
          return;
        }

        // Update token and connect
        UpdateToken(client, refreshToken, accessToken);
      });

A stored refresh token can become invalid between sessions for several reasons:

In every one of these cases the response on /oauth2/token with grant_type=refresh_token is identical:

  • HTTP status: 400
  • Body: { "error": "invalid_grant", "error_description": "Invalid \"refresh_token\" in request" }

There is no ban-specific or unlink-specific error code on this path. invalid_grant only tells you the refresh token is no longer valid server-side — not why — so you cannot tell from the error alone which of the situations above you are in.

How you recover depends on whether the user still has a linked Discord account. Because the invalid_grant error doesn't distinguish the cases, the robust pattern is to attempt the provisional account fallback first, then handle error 530010:

  1. Drop the stored Discord tokens for the user and attempt the provisional account flow.
  2. If the provisional flow succeeds, a revocation, unmerge, or ban had already reverted the linked account to a provisional account. The player stays in-game even though their Discord link is gone.
  3. If the provisional flow returns error code 530010 — User account is non-provisional and should be authed through OAuth2 — the tokens simply expired or became invalid and the user still has a linked Discord account. The token-exchange endpoint deliberately blocks linked users from the provisional path. Prompt the player to re-run the OAuth2 authorization flow to obtain fresh tokens instead.

A user's authorization for your app can be revoked in several ways — by your own backend, by the user from Discord's UI, or by Discord itself when an account is banned. Regardless of how the revocation is triggered:

  • The user's access and refresh tokens are immediately invalidated. Any subsequent refresh on /oauth2/token returns HTTP 400 with error: "invalid_grant" (see When Refresh Fails).
  • An APPLICATION_DEAUTHORIZED webhook is fired to your app.

You'll naturally know about revocations your own code initiates. For revocations triggered outside your code — see Out-of-Band Revocation below — the webhook is the only proactive way to find out.

If your application uses a backend server, you can revoke tokens by making an API request to Discord's token revocation endpoint.

Python
import requests

API_ENDPOINT = 'https://discord.com/api/v10'
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'

def revoke_token(access_or_refresh_token):
    data = {'token': access_or_refresh_token}
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    r = requests.post(f'{API_ENDPOINT}/oauth2/token/revoke', data=data, headers=headers, auth=(CLIENT_ID, CLIENT_SECRET))
    r.raise_for_status()

The easiest way to revoke tokens is using the SDK's Client::RevokeToken method. This will invalidate all access and refresh tokens for the user and they cannot be used again.

C++
client->RevokeToken(YOUR_DISCORD_APPLICATION_ID,
                    accessToken, // Can also use refresh token
                    [](const discordpp::ClientResult &result) {
                      if (!result.Successful()) {
                        std::cout
                            << "? Error revoking token: " << result.Error()
                            << std::endl;
                        return;
                      }

                      std::cout
                          << "? Token successfully revoked! User logged out."
                          << std::endl;
                      // Handle successful logout (clear stored tokens,
                      // redirect to login, etc.)
                    });

Two paths sever the user's Discord link without your code calling any unmerge or revoke endpoint:

  • The user removes your app from their Discord User Settings → Authorized Apps page (a user-initiated unmerge).
  • The user's Discord account is banned (a ban-driven unmerge, with an additional cross-platform-restricted state on the new provisional account).

Subscribe to APPLICATION_DEAUTHORIZED to be notified when either happens. If you miss the webhook, the next token use will fail and your game should fall back to the provisional account flow.


The recommended path for integrating the Discord Social SDK is that your game has a primary authentication other than Discord that initially sets up a provisional account, and have the player link their Discord account to this primary authentication.

This approach protects your users' game access and data if they encounter issues with their Discord account, such as a permanent or temporary ban. To implement this recommended path:

  1. Create an account through a non-Discord authentication provider, and create a provisional account attached to it.
  2. When users later authenticate through Discord to link their account, have your game back end execute the merge their provisional account with their Discord Account.
  3. The account merging process will internally store the externalAuthToken from the provisional account against their Discord account. If a ban of the Discord account happens, that externalAuthToken will be attached to the new provisional account that is created in its stead, with the original Discord account's in-game friends, and will be available through the authentication provider the account was initially setup with.
  4. As a last step, your game back end should maintain the record of the externalAuthToken against the user account, even after the account merging process, since it will be needed to authenticate via a provisional account should Discord authentication fails for a ban, or any other reason.

Now that you've successfully implemented account linking with Discord, you can integrate more social features into your game.

Need help? Join the Discord Developers Server and share questions in the #social-sdk-dev-help channel for support from the community.

If you encounter a bug while working with the Social SDK, please report it here: https://dis.gd/social-sdk-bug-report


Date Changes
June 9, 2026 Clarify invalid_grant recovery and the 530010 error
May 22, 2026 Add out-of-band revocation and recommended integration path
March 17, 2025 initial release
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu