Laravel Authentication

Laravel is a popular open-source PHP web application framework designed to simplify and accelerate web development. It provides a robust set of tools and features to help developers build web applications with clean, maintainable, and scalable code.

PropelAuth seamlessly integrates with Laravel via Socialite and PropelAuth's OAuth2 Support, offering developers an efficient way to implement secure and customizable authentication.

Here is more information on the PropelAuth Socialite Provider.

Setup in PropelAuth

We first need to create an OAuth2 provider within PropelAuth. To do so, navigate to the Frontend Integration page in your PropelAuth dashboard, click on Advanced Settings then Edit OAuth Config.

Navigating to OAuth2 Dashboard Page

Create a New OAuth Client and copy down your Client ID and Client Secret. We'll be needing these for later.

While we're here, let's also add a Redirect URI. In this guide we'll be using http://localhost:8000/auth/callback.

OAuth2 Dashboard Page

The last variable we need is our Auth URL. You can find this back in the Frontend Integration page.

Installation

Start by installing the PropelAuth Socialite provider in your Laravel project:

composer require socialiteproviders/propelauth

Let's now add some environment variables to your .env. All four of these variables were retrieved in the previous step.

PROPELAUTH_CLIENT_ID=0166...
PROPELAUTH_CLIENT_SECRET=f92ac31...
PROPELAUTH_CALLBACK_URL=http://localhost:8000/auth/callback
PROPELAUTH_AUTH_URL=https://auth.example.com

Next, head over to config/services.php and add the PropelAuth provider.

'propelauth' => [
  'client_id' => env('PROPELAUTH_CLIENT_ID'),
  'client_secret' => env('PROPELAUTH_CLIENT_SECRET'),
  'redirect' => env('PROPELAUTH_CALLBACK_URL'),
  'auth_url' => env('PROPELAUTH_AUTH_URL'),
],

We now need to register an event listener to extend Socialite with the PropelAuth provider. This will allow us to authenticate users via PropelAuth in the same ways you can with any other Socialite provider.

// /app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
    }

    public function boot(): void
    {
        Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
            $event->extendSocialite('propelauth', \SocialiteProviders\PropelAuth\Provider::class);
        });
    }
}

Routing

To authenticate users using PropelAuth's OAuth2 provider we first need to create two routes. The first is for redirecting unauthenticated users to login and the other is for handling the OAuth callback after authentication.

The /auth/callback endpoint is also responsible for saving the user to your database or saving the user's information in the session. This part is up to you, but in this example we'll be saving the user's data using Laravel's HTTP Session. For alternative methods see the Laravel docs here.

If you would like to customize how user information is mapped in the /auth/callback route see the User Array for more information on what user information is available.

use Illuminate\Support\Facades\Route;
use Laravel\Socialite\Facades\Socialite;

Route::get('/auth/redirect', function () {
    return Socialite::driver('propelauth')->redirect();
});
 
Route::get('/auth/callback', function () {
    $user = Socialite::driver('propelauth')->user();
    session(['refresh_token' => $user->refreshToken]);
    session(['access_token' => $user->token]);
    session(['user' => $user->getRaw()]);
    return redirect('/');
});

In the example above we also store the user's refresh_token and access_token. We'll be needing these later to refresh the user's information and successfully log the user out of your app.

Protecting Routes and Displaying User information

Since we have stored the user's information in the session we can run a quick check if the user data is saved. If it is we can pass it along to the view. Otherwise, redirect the user to login via the /auth/redirect route.

Route::get('/', function () {
    $user = session('user');
    if (!$user) {
        return redirect('/auth/redirect');
    }
    return view('welcome', ['user' => $user]);
});

Refreshing User information

If you want the most up to date user information you can use Socialite's userFromToken method. This method automatically makes a request to the User Info Endpoint for you, providing you with a new and up to date User Array.

$accessToken = session('access_token');
$user = Socialite::driver('propelauth')->userFromToken($accessToken);
session(['user' => $user->getRaw()]);

PropelAuth's access_token is valid for a total of 30 minutes, so the userFromToken will eventually return a 401 error if it's not refreshed periodically. To do so, you can make a request to the Refresh Token Endpoint to retrieve a new access and refresh token.

use Illuminate\Support\Facades\Http;
use Laravel\Socialite\Facades\Socialite;

$accessToken = session('access_token');

try {
    // Attempt to fetch the user info with the current access token
    $user = Socialite::driver('propelauth')->userFromToken($accessToken);
    session(['user' => $user->getRaw()]);
} catch (\Exception $e) {
    if ($e->getCode() !== 401) {
        return redirect('/auth/redirect');
    }

    // Handle token refresh
    $refreshToken = session('refresh_token');
    $response = Http::asForm()->post(env('PROPELAUTH_AUTH_URL') . '/propelauth/oauth/token', [
        'grant_type' => 'refresh_token',
        'refresh_token' => $refreshToken,
        'client_id' => env('PROPELAUTH_CLIENT_ID'),
    ]);

    if (!$response->ok()) {
        return redirect('/auth/redirect');
    }

    $data = $response->json();

    // Update session with new tokens
    session(['refresh_token' => $data['refresh_token']]);
    session(['access_token' => $data['access_token']]);

    // Retry fetching the user with the new access token
    try {
        $user = Socialite::driver('propelauth')->userFromToken($data['access_token']);
        session(['user' => $user->getRaw()]);
    } catch (\Exception $retryException) {
        return redirect('/auth/redirect');
    }
}

Authorization / Organizations

You can also verify which organizations the user is in, and which roles and permissions they have in each organization.

Check Org Membership

Verify that the request was made by a valid user and that the user is a member of the specified organization.

Route::get('/org/{orgId}', function ($orgId) {
    $user = session('user');
    if (!$user) {
        return redirect('/auth/redirect');
    }
    if (
        isset($user['org_id_to_org_info'][$orgId])
    ) {
        return view('welcome', ['user' => $user]);
    } else {
        return "You do not belong to this org";
    }
});

Check Org Membership and Role

Similar to checking org membership, but will also verify that the user has a specific Role in the organization.

A user has a Role within an organization. By default, the available roles are Owner, Admin, or Member, but these can be configured. These roles are also hierarchical, so Owner > Admin > Member.

Route::get('/org/{orgId}', function ($orgId) {
    $user = session('user');
    if (!$user) {
        return redirect('/auth/redirect');
    }
    $roleToCheck = "Admin";
    if (
        isset($user['org_id_to_org_info'][$orgId]) &&
        $user['org_id_to_org_info'][$orgId]['user_role'] === $roleToCheck
    ) {
        return view('welcome', ['user' => $user]);
    } else {
        return "You must be an Admin to access this page";
    }
});

Check Org Membership and Permission

Similar to checking org membership, but will also verify that the user has the specified permission in the organization.

Permissions are arbitrary strings associated with a role. For example, can_view_billing, ProductA::CanCreate, and ReadOnly are all valid permissions. You can create these permissions in the PropelAuth dashboard.

Route::get('/org/{orgId}', function ($orgId) {
    $user = session('user');
    if (!$user) {
        return redirect('/auth/redirect');
    }
    $permissionToCheck = "can_view_billing";
    if (
        isset($user['org_id_to_org_info'][$orgId]) &&
        in_array(
            $permissionToCheck,
            $user['org_id_to_org_info'][$orgId]['user_permissions'] ?? []
        )
    ) {
        return view('welcome', ['user' => $user]);
    } else {
        return "You do not have the correct permissions to view this page";
    }
});

Logging Out

Logging a user out of your application requires two steps. The first is using the refresh_token we saved earlier to make a request to PropelAuth. This will invalidate the refresh_token as well as log the user out of any hosted pages.

The second step depends on how you logged the user into your app previously. Since we have been using session storage in this guide, we'll delete the user information and refresh token from the session storage after making the request to PropelAuth.

This can all easily be done by creating a new route in your Laravel application and redirecting your users to it.

Route::get('/auth/logout', function () {
    // get refresh token
    $refresh_token = session('refresh_token');

    // delete user and refresh token from session
    session()->forget(['refresh_token', 'user']);

    if (!$refresh_token) {
        return redirect('/auth/redirect');
    }

    $response = Http::withHeaders([
        'Content-Type' => 'application/json',
    ])->post(env('PROPELAUTH_AUTH_URL') . '/api/backend/v1/logout', [
        'refresh_token' => $refresh_token,
    ]);

    if ($response->failed()) {
        return response()->json(['error' => 'Failed to log out'], 400);
    }

    return redirect('/');
});

User Array

By default, only the user's email and id are returned after logging in. However, you can get all the user information by retrieving the raw user array:

$user = Socialite::driver('propelauth')->user();
$rawUser = $user->getRaw();

This returns:

Array
(
    [can_create_orgs] => null,
    [created_at] => 1730301670,
    [email] => 'test@example.com',
    [email_confirmed] => 1,
    [enabled] => 1,
    [first_name] => 'Buddy',
    [last_name] => 'Framm',
    [has_password] => 1,
    [last_active_at] => 1733774618,
    [locked] => null,
    [mfa_enabled] => null,
    [sub] => '31c41c16-c281-44ae-9602-8a047e3bf33d',
    [update_password_required] => null,
    [user_id] => '31c41c16-c281-44ae-9602-8a047e3bf33d',
    [username] => 'airbud3',
    [picture_url] => 'https://example.com/picture.jpg',
    [properties] => Array
    (
        [favoriteSport] => 'Basketball',
    ),
    [org_id_to_org_info] => Array
    (
        [1189c444-8a2d-4c41-8b4b-ae43ce79a492] => Array
        (
            [additional_roles] => Array(),
            [inherited_user_roles_plus_current_role] => Array
            (
                [0] => 'Admin',
                [1] => 'Member',
            ),
            [org_id] => '1189c444-8a2d-4c41-8b4b-ae43ce79a492',
            [org_metadata] => Array
            (
                [customKey] => 'customValue',
            ),
            [org_name] => 'Acme Inc',
            [org_role_structure] => 'single_role_in_hierarchy',
            [url_safe_org_name] => 'acme-inc',
            [user_permissions] => Array
            (
                [0] => 'propelauth::can_manage_api_keys',
                [1] => 'can_edit_billing',
            ),
            [user_role] => 'Admin',
        ),
    ),
)