Skip to content

Instantly share code, notes, and snippets.

@RikoDEV
Last active December 3, 2023 09:06
Show Gist options
  • Save RikoDEV/e6a35ce7cb7fb9880537c0b6e19b35db to your computer and use it in GitHub Desktop.
Save RikoDEV/e6a35ce7cb7fb9880537c0b6e19b35db to your computer and use it in GitHub Desktop.
Laravel + Jetstream + Socialstream Discord Linked Roles

discord linked roles

How to easily add linked roles support for Socialstream login using Discord?

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.

Requirements

  • Laravel 10+
  • Jetstream v3+
  • Socialstream v4+ | v5+
  • SocialiteProviders/Discord

Before...

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
	}
]'

Let's do this!

  1. Copy socialstream/src/Actions/Auth/AuthenticateOauthCallback.php controller to your Http/Controllers. Override callback logic using https://docs.socialstream.dev/getting-started/overriding-the-callback-logic#override-callback-logic.
  2. 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
            ]
        ]);

    }
}
  1. 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;
}
  1. 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. 😉
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment