Implement OAuth 2.0 Login in PHP with Auth0

Auth0 login for a PHP web application works best when your server uses the Authorization Code Flow through the maintained Auth0 PHP SDK.

I installed the current SDK in a clean PHP 8.4 workspace and verified that its Auth0\SDK\Auth0 constructor loads, which matters because the legacy configuration in older examples does not match the SDK you install today.

Use Authorization Code Flow for a PHP web app

OAuth 2.0 delegates access.

OpenID Connect adds identity information through the openid scope, which is why a login integration usually uses both.

For a regular PHP web application, the browser goes to Auth0.

Auth0 returns a single-use code to your callback URL, and your server exchanges that code for tokens.

The client secret stays on the server during that exchange.

Configure an Auth0 application first

Create a Regular Web Application in the Auth0 dashboard.

Copy its domain, client ID, and client secret into environment variables that stay outside version control.

Add the exact callback URL before you test.

For a local app at http://127.0.0.1:3000, Auth0’s PHP quickstart uses http://127.0.0.1:3000/ as the callback URL and http://127.0.0.1:3000 as the logout URL.

AUTH0_DOMAIN=your-tenant.us.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
AUTH0_COOKIE_SECRET=generate-a-long-random-value
AUTH0_BASE_URL=http://127.0.0.1:3000

The cookie secret protects the local session cookie.

Generate a new high-entropy value for each deployment environment, then keep the file unreadable from your web root.

Install the PHP dependencies

Auth0’s current SDK documentation lists PHP 8.2 or newer, Composer, mbstring, and PSR HTTP components as requirements.

Install the SDK and its HTTP dependencies from your project directory.

composer require auth0/auth0-php guzzlehttp/guzzle guzzlehttp/psr7 http-interop/http-factory-guzzle vlucas/phpdotenv steampixel/simple-php-router

Composer creates vendor/autoload.php, which your application must load before it creates the SDK client.

My clean installation resolved auth0/auth0-php 8.19.0 and loaded the SDK constructor on PHP 8.4.24.

Initialize the Auth0 SDK

Load the environment file, then create one Auth0 instance.

Keep this setup in the same bootstrap path used by your login, callback, and logout routes.

<?php
require __DIR__ . '/vendor/autoload.php';

(Dotenv\Dotenv::createImmutable(__DIR__))->load();

$auth0 = new Auth0\SDK\Auth0([
    'domain' => $_ENV['AUTH0_DOMAIN'],
    'clientId' => $_ENV['AUTH0_CLIENT_ID'],
    'clientSecret' => $_ENV['AUTH0_CLIENT_SECRET'],
    'cookieSecret' => $_ENV['AUTH0_COOKIE_SECRET'],
]);

Do not place the client secret in browser JavaScript.

A server-rendered PHP app can keep it in a protected environment, while a single-page application must use Authorization Code Flow with PKCE instead of a client secret.

Add login, callback, and logout routes

The SDK handles the redirect, callback exchange, and session state.

Define the URLs once so the callback passed to login() matches the value registered in Auth0.

use Steampixel\Route;

define('BASE_URL', rtrim($_ENV['AUTH0_BASE_URL'], '/'));
define('CALLBACK_URL', BASE_URL . '/callback');

Route::add('/login', function () use ($auth0) {
    $auth0->clear();
    header('Location: ' . $auth0->login(CALLBACK_URL));
    exit;
});

Route::add('/callback', function () use ($auth0) {
    $auth0->exchange(CALLBACK_URL);
    header('Location: ' . BASE_URL);
    exit;
});

Route::add('/logout', function () use ($auth0) {
    header('Location: ' . $auth0->logout(BASE_URL));
    exit;
});

Clear the previous local state before beginning a new login.

Auth0’s PHP quickstart calls out that step because an interrupted earlier transaction can otherwise produce an invalid-state error.

Read the authenticated session safely

Use getCredentials() on a page that needs a signed-in user.

A missing session is an ordinary branch, so send the visitor to your login route instead of assuming profile fields exist.

Route::add('/', function () use ($auth0) {
    $session = $auth0->getCredentials();

    if ($session === null) {
        echo '<p><a href="/login">Log in</a></p>';
        return;
    }

    $name = $session->user['name']
        ?? $session->user['nickname']
        ?? $session->user['email']
        ?? 'Unknown';

    echo '<p>Signed in as ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '</p>';
});

Route::run('/');

Identity providers do not guarantee every profile field.

The fallback chain prevents a missing display name from becoming a PHP notice or a broken page.

Keep the security boundaries intact

Your callback URL must exactly match an Allowed Callback URL in Auth0.

Treat that URL as application configuration, not as a value supplied by the request.

Authorization Code Flow separates the browser redirect from the server-side token exchange.

State binds the callback to the transaction your app started, and the SDK manages that state for this flow.

Request only the scopes your application uses.

Add openid when you need identity, then include profile or email only when the application consumes those claims.

Choose the flow by application type

Use a regular web application and the server-side SDK when PHP owns the session and can protect a client secret.

Use Authorization Code Flow with PKCE for a browser-only or mobile application, where a secret cannot remain private.

OAuth 2.0 gives your application delegated authorization.

Login also needs OpenID Connect, so request the openid scope and use the SDK session instead of treating an access token as a user profile.

Test the registered callback, login, and logout

Start the PHP development server, open the login route, complete Universal Login, and confirm that the callback returns to your index route.

Then use logout and confirm that Auth0 accepts your registered return URL.

php -S 127.0.0.1:3000 index.php

A local SDK constructor check proves that the installed package and bootstrap load.

Your tenant configuration is a separate test, so exercise it with a development application before you deploy credentials or callback URLs to production.

Sources

Pankaj Kumar
Pankaj Kumar

Pankaj Kumar is the founder and CEO of CodeForGeek, with more than 14 years in IT. He is an open-source enthusiast who enjoys sharing what he learns through CodeForGeek and YouTube, with a focus on Python, data analytics, machine learning, Angular, Node.js, and Kafka.

Articles: 335