Linked roles is a new solution for assigning roles to a user after linking the account to the selected application. Since my Laravel site already uses Discord login, why not use it? The implementation itself seems complicated, but it is a matter of adding a few lines of code. Simple.
- Laravel 10+
- Jetstream v3+
- Socialstream v4+ | v5+
- SocialiteProviders/Discord
Before starting, make sure to set up the metadata that will be used in the integration in the next steps. Of course, I'm assuming you know how Discord bots work, where to get the bot token and application ID....
https://support.discord.com/hc/en-us/articles/10388356626711-Connections-Linked-Roles-Admins
Example using curl:
curl --request PUT \
--url https://discord.com/api/v10/applications/<APP_ID>/role-connections/metadata \
--header 'Authorization: Bot <DISCORD_BOT_TOKEN>' \
--header 'Content-Type: application/json' \
--data '[
{
"key": "total_posts",
"name": "Number of user posts.",
"name_localizations": { // Optional
"pl": "Liczba postów"
},
"description": "Number of user posts.",
"description_localizations": { // Optional
"pl": "Liczba postów użytkownika."
},
"type": 2
},
{
"key": "total_comments",
"name": "Number of comments",
"name_localizations": { // Optional
"pl": "Liczba komentarzy"
},
"description": "Number of user comments.",
"description_localizations": { // Optional
"pl": "Liczba komentarzy użytkownika"
},
"type": 2
}
]'
- Copy
socialstream/src/Actions/Auth/AuthenticateOauthCallback.php
controller to yourHttp/Controllers
. Override callback logic using https://docs.socialstream.dev/getting-started/overriding-the-callback-logic#override-callback-logic. - Add some magic code to copied controller:
<?php
namespace App\Http\Controllers;
...
use Illuminate\Support\Facades\Http;
class AuthenticateOauthCallback implements AuthenticatesOauthCallback
{
...
public function authenticate(string $provider, ProviderUser $providerAccount): Response|RedirectResponse|LoginResponse
{
...
$user->forceFill([
'current_connected_account_id' => $account->id,
])->save();
// Handle discord linked roles
if ($provider == 'discord') {
$this->handleDiscordLinkedRoles($account, $providerAccount);
}
return $this->login($user);
}
/**
* Handle connection of accounts for an already authenticated user.
* @noinspection PhpUndefinedFieldInspection
*/
protected function alreadyAuthenticated(Authenticatable $user, ?ConnectedAccount $account, string $provider, ProviderUser $providerAccount): RedirectResponse
{
...
// Handle discord linked roles
if ($provider == 'discord') {
$this->handleDiscordLinkedRoles($account, $providerAccount);
}
return redirect()->route('profile.show')->dangerBanner(
__('This :Provider sign in account is already associated with your user.', ['provider' => $provider]),
);
}
...
protected function handleDiscordLinkedRoles($account, ProviderUser $providerAccount): void
{
$url = "https://discord.com/api/v10/users/@me/applications/". config('services.discord.client_id') ."/role-connection";
Http::withToken($providerAccount->token)->withHeaders([
'Content-Type' => 'application/json'
])->put($url, [
'platform_name' => 'Your app name',
'metadata' => [ // Here you can set the metadata from the beginning
'total_posts' => 4,
'total_comments' => 8
]
]);
}
}
- Now modify
app/Actions/Socialstream/GenerateRedirectForProvider.php
:
<?php
declare(strict_types=1);
namespace App\Actions\Socialstream;
use JoelButcher\Socialstream\Contracts\GeneratesProviderRedirect;
use JoelButcher\Socialstream\Socialstream;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\RedirectResponse;
class GenerateRedirectForProvider implements GeneratesProviderRedirect
{
/**
* Generates the redirect for a given provider.
*/
public function generate(string $provider): RedirectResponse
{
// Extra: prevent missing provider exceptions
if (array_contains($provider, Socialstream::providers())) { // SocialStream v4
if (array_contains($provider, data_get(Socialstream::providers(), '*.id'))) { // SocialStream v5
if ($provider == 'discord') {
return Socialite::driver($provider)->scopes([
'role_connections.write', 'identify', 'email'
])->redirect();
}
return Socialite::driver($provider)->redirect();
}
abort(404);
}
}
Array_contains
helper:
/**
* Check if a string contains any elements from an array.
*
* @param string $str The string to search in.
* @param array $arr The array of elements to search for.
* @return bool Returns true if the string contains any of the elements from the array, otherwise false.
*/
function array_contains(string $str, array $arr): bool
{
foreach ($arr as $a) {
if (false !== mb_stripos($str, $a)) {
return true;
}
}
return false;
}
- Done. Now open Discord Developer Portal and set LINKED ROLES VERIFICATION URL to
https://example.com/oauth/discord
. After configuring the linked roles on the Discord server, try linking the accounts. 😉