Discord OAuth2 Flow in Laravel
Damion Voshall
Software Engineer @ DiscordBrowser | Contributor to Larascord, a Laravel Discord API wrapper | PHP Laravel Full-Stack Software Solutions
#Discord #Laravel #Programming #WebDevelopment #PHP
So, you may be wondering how sites let you log in with your Discord profile. It can be a daunting and confusing process if you've never worked with OAuth before, or if you're not familiar with REST API's.
The first steps start in Discord itself. Head over to the Discord Developer Portal and create an application.
Inside the application you can new create your scopes and your redirect uri.
For now, just turn on the identify scope, and make sure, this is your redirect uri if you are running a local development server:
Now that you have your redirect uri, get your client secret and client id in the root of your application, do NOT share your client secret.
At this point you may decide to use a pre-made package. As a contributor, I suggest using the Larascord Package.
If you decide not to use the package, then you may continue following along to learn how this package works under the hood.
After creating or opening your existing Laravel project, add the following to your .env:
Access these variables with a config file.
After assigning each of your new .env variables, be sure to clear your config cache.
php artisan config:clear
Let's set up the route for the login.
The following url will look something like this:
Route::redirect('/login', 'https://discord.com/oauth2/authorize?client_id=' . config('config.client_id')
. '&redirect_uri=' . config('config.redirect_uri')
. '&response_type=code&scope=' . implode('%20', explode('&', config('config.scopes')))
. '&prompt=' . config('config.prompt', 'none'))
->name('login');
When going to /login, your users will be redirected to the Discord OAuth login. This url that is built will look just like the url the Discord application provides for you.
领英推荐
Add the following routes as well:
Route::group(['prefix' => config('config.route_prefix', 'discordbrowser')], function() {
Route::get('/callback', [DiscordController::class, 'handle'])
->name('discord.login');
Route::redirect('/refresh-token', url('login'))
->name('discord.refresh_token');
});
The following will trigger a 'handle' method in the DiscordController. This is where we will access the token, and use it to fetch the user's information.
The following 'handle' method will look something like this:
use App\Http\Requests\StoreUserRequest;
...
public function handle(StoreUserRequest $request): RedirectResponse | JsonResponse
{
//Get access token from discord api
try {
$accessToken = (new DiscordService())->getAccessTokenFromCode($request->get('code'));
} catch (\Exception $e) {
return $this->throwError('invalid_code', $e);
}
A custom exception can be created if you please. This also uses a DiscordService to fetch the AccessToken and assigns it to the $accessToken variable.
The DiscordService will now have to be created. We will start by creating a 'getAccessTokenFromCode' method within this file:
class DiscordService
{
//Discord oauth service url
protected string $tokenURL = 'https://discord.com/api/oauth2/token';
//Discord api url
protected string $baseApi = 'https://discord.com/api';
//Discord Bot Token
protected string $botToken;
//Token request required data
protected array $tokenData = [
'client_id' => NULL,
'client_secret' => NULL,
'grant_type' => 'authorization_code',
'code' => NULL,
'redirect_uri' => NULL,
'scope' => NULL,
];
//User service constructor
public function __construct()
{
$this->tokenData['client_id'] = config('config.client_id');
$this->tokenData['client_secret'] = config('config.client_secret');
$this->tokenData['redirect_uri'] = config('config.redirect_uri');
$this->tokenData['scope'] = config('config.scopes');
$this->botToken = config('config.bot_token');
}
//Handles oauth2 callback and returns access token
public function getAccessTokenFromCode(string $code): AccessToken
{
$this->tokenData['code'] = $code;
$response = Http::asForm()->post($this->tokenURL, $this->tokenData);
$response->throw();
return new AccessToken(json_decode($response->body()));
}
This class is constructing the tokenData based on the info in your .env, and assigning the 'code' property passed in from the 'handle' method in the DiscordController. When you dd(json_decode($response->body()), you will receive an object that looks something like this:
Congratulations! You're almost there, you can see I created an AccessToken Type that stores the data received from the Discord API. This object is then linked to the $accessToken variable created in the DiscordController.
Time to fetch the user's data with the newly created AccessToken.
Back to the DiscordController within the 'handle' method. Add in this block of code:
// get user from discord api
try {
$user = (new DiscordService())->getCurrentUser($accessToken);
$user->setAccessToken($accessToken);
} catch (\Exception $e) {
return $this->throwError('authorization_failed', $e);
}
This block fetches a user object from the Discord API using their access token. As you can see we use another method from the DiscordService called 'getCurrentUser'. Let's create that method now:
//Authenticate user with access token and return user data
public function getCurrentUser(AccessToken $accessToken): UserType
{
$response = Http::withToken($accessToken->access_token)->get($this->baseApi . '/users/@me');
$response->throw();
return new UserType(json_decode($response->body()));
}
This method fetches the user data from the Discord API with their access token. To see the data fetched, you can dd(json_decode($response->body());
This will return an object that looks like this:
As you can see, this data is also assigned to a user type, and this object is linked to the $user variable in the DiscordController.
Congratulations! You have successfully integrated Discord's OAuth2 functionality into your application!
You can now use this $user variable to store data in your db, display it, or create whatever flows you need!
Please check out Larascord and give it a star! Very awesome and well thought out package.
Also check out our site that uses many aspects of the Discord API: DiscordBrowser.
Here is a link to the Discord documentation to add more features to your application! Discord API Documentation.
If you have any questions, feel free to shoot a message! Here's a link to my personal Github.