Skip to content

Office365SSO

Ray Fung edited this page Feb 26, 2026 · 3 revisions

Office365SSO

Office365SSO extends the base OAuth2 class to provide Microsoft Entra ID (formerly Azure AD) Single Sign-On with Microsoft Graph API integration. It handles the full OAuth 2.0 authorisation code flow, ID token parsing, and user profile retrieval.


Table of Contents


Quick Start

use Razy\Office365SSO;



$sso = new Office365SSO(

    clientId:     'your-app-client-id',

    clientSecret: 'your-app-client-secret',

    redirectUri:  'https://app.example.com/auth/callback',

    tenantId:     'your-tenant-id',

);



// Step 1: Redirect user to Microsoft login

header('Location: ' . $sso->getAuthorizeUrl());

exit;



// Step 2: Handle callback (in /auth/callback)

$sso->requestAccessToken($_GET['code']);



// Step 3: Get user info

$email = $sso->getUserEmail();

$name  = $sso->getUserDisplayName();

Configuration

Constructor

$sso = new Office365SSO(

    clientId:     'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',

    clientSecret: 'your-client-secret-value',

    redirectUri:  'https://app.example.com/auth/callback',

    tenantId:     'your-tenant-id',  // or 'common' for multi-tenant

);

Microsoft Entra ID URLs

The Office365SSO class automatically builds Microsoft Entra ID endpoints from the tenant ID:

| Endpoint | URL Pattern |

| --- | --- |

| Authorise | https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize |

| Token | https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token |

| Sign-out | https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/logout |

Multi-Tenant Support

// Use 'common' for any Microsoft account or Azure AD account

$sso = new Office365SSO(

    clientId:     'xxx',

    clientSecret: 'xxx',

    redirectUri:  'https://app.example.com/auth/callback',

    tenantId:     'common',

);



// Use 'consumers' for personal Microsoft accounts only

// Use 'organizations' for Azure AD accounts only

Authentication Flow

Authorisation URL

// Basic

$url = $sso->getAuthorizeUrl();



// With state parameter (CSRF protection)

$state = bin2hex(random_bytes(16));

$_SESSION['oauth_state'] = $state;

$url = $sso->getAuthorizeUrl(state: $state);



header('Location: ' . $url);

exit;

Token Exchange

// In the callback handler

if ($_GET['state'] !== $_SESSION['oauth_state']) {

    throw new \RuntimeException('State mismatch');

}



// Exchange authorisation code for access token

$sso->requestAccessToken($_GET['code']);



// The access token is stored internally

// You can also get the raw token data:

$token = $sso->getAccessToken();         // access_token string

$refresh = $sso->getRefreshToken();      // refresh_token (if offline_access scope)

$expires = $sso->getTokenExpiresAt();    // DateTime

Session Management

// Create a session from the current token

$sso->createSession();

// Stores token data in $_SESSION for subsequent requests



// Check if session has expired

if (Office365SSO::isSessionExpired()) {

    // Re-authenticate

    header('Location: ' . $sso->getAuthorizeUrl());

    exit;

}

User Profile

Graph API Methods

After authentication, retrieve user profile data from Microsoft Graph:

// Full user info (all available fields)

$userInfo = $sso->getUserInfo();

/*

[

    'displayName'    => 'Alice Smith',

    'mail'           => 'alice@example.com',

    'givenName'      => 'Alice',

    'surname'        => 'Smith',

    'jobTitle'       => 'Software Engineer',

    'userPrincipalName' => 'alice@example.onmicrosoft.com',

    ...

]

*/



// Individual fields

$email       = $sso->getUserEmail();

$displayName = $sso->getUserDisplayName();

$firstName   = $sso->getUserGivenName();

$lastName    = $sso->getUserSurname();

$jobTitle    = $sso->getUserJobTitle();



// Profile photo (binary data)

$photoData = $sso->getUserPhoto();

if ($photoData) {

    file_put_contents('avatar.jpg', $photoData);

}

ID Token Parsing

The ID token returned by Microsoft Entra ID contains user claims. You can parse and validate it:

// Parse the ID token (JWT)

$claims = $sso->parseIdToken();

/*

[

    'aud' => 'client-id',

    'iss' => 'https://login.microsoftonline.com/{tenant}/v2.0',

    'sub' => 'user-subject-id',

    'email' => 'alice@example.com',

    'name'  => 'Alice Smith',

    'preferred_username' => 'alice@example.com',

    'tid'   => 'tenant-id',

    'iat'   => 1705350000,

    'exp'   => 1705353600,

    ...

]

*/



// Validate the ID token signature and claims

$valid = $sso->validateIdToken();

// Returns true if token is valid, unexpired, and audience matches

Advanced Configuration

Graph Scope

Control which Microsoft Graph permissions are requested:

// Default scope includes: openid, profile, email, User.Read

$sso->setGraphScope('openid profile email User.Read Mail.Read');



// Access calendar

$sso->setGraphScope('openid profile email User.Read Calendars.Read');



// Get current scope

$scope = $sso->getGraphScope();

Login Hints

Pre-fill the email field on the Microsoft login page:

$sso->setLoginHint('alice@example.com');

$url = $sso->getAuthorizeUrl();

// Login page shows alice@example.com pre-filled

Prompt Behaviour

Control the authentication prompt:

// Force re-authentication (even if already signed in)

$sso->setPrompt('login');



// Show consent prompt

$sso->setPrompt('consent');



// Silent authentication (no UI, fails if interaction needed)

$sso->setPrompt('none');



// Default (Microsoft decides based on session state)

$sso->setPrompt('select_account');

Domain Hint

Hint the Azure AD tenant for faster login:

$sso->setDomainHint('example.com');

// Skips the "pick an account" screen for users in this domain

Sign-Out

// Get the sign-out URL

$signOutUrl = Office365SSO::getSignOutUrl(

    tenantId: 'your-tenant-id',

    postLogoutRedirectUri: 'https://app.example.com/logged-out',

);



// Redirect user to sign out

header('Location: ' . $signOutUrl);

exit;

This signs the user out of both your application and Microsoft's session.


Full Example

Complete SSO Integration

use Razy\Office365SSO;



// config.php

$sso = new Office365SSO(

    clientId:     getenv('AZURE_CLIENT_ID'),

    clientSecret: getenv('AZURE_CLIENT_SECRET'),

    redirectUri:  getenv('AZURE_REDIRECT_URI'),

    tenantId:     getenv('AZURE_TENANT_ID'),

);



// login.php → Initiate login

session_start();

$_SESSION['oauth_state'] = bin2hex(random_bytes(16));



$sso->setPrompt('select_account');

$sso->setGraphScope('openid profile email User.Read');



header('Location: ' . $sso->getAuthorizeUrl(state: $_SESSION['oauth_state']));

exit;



// callback.php → Handle Microsoft response

session_start();



if ($_GET['state'] !== $_SESSION['oauth_state']) {

    die('Invalid state parameter');

}



try {

    $sso->requestAccessToken($_GET['code']);

    $sso->createSession();

    

    $userInfo = $sso->getUserInfo();

    

    // Find or create local user

    $user = User::firstOrCreate(

        ['email' => $sso->getUserEmail()],

        [

            'name'       => $sso->getUserDisplayName(),

            'first_name' => $sso->getUserGivenName(),

            'last_name'  => $sso->getUserSurname(),

            'job_title'  => $sso->getUserJobTitle(),

        ],

    );

    

    $_SESSION['user_id'] = $user->id;

    header('Location: /dashboard');

} catch (\Razy\Exception\OAuthException $e) {

    error_log('SSO failed: ' . $e->getMessage());

    header('Location: /login?error=sso_failed');

}



// logout.php → Sign out

session_start();

session_destroy();



header('Location: ' . Office365SSO::getSignOutUrl(

    tenantId: getenv('AZURE_TENANT_ID'),

    postLogoutRedirectUri: getenv('APP_URL') . '/login',

));

exit;



// middleware → Check session on protected routes

if (Office365SSO::isSessionExpired()) {

    header('Location: /login');

    exit;

}

API Reference

Office365SSO (extends OAuth2)

| Method | Signature | Returns |

| --- | --- | --- |

| __construct | (string $clientId, string $clientSecret, string $redirectUri, string $tenantId) | |

| getTenantId | (): string | Tenant ID |

| setGraphScope | (string $scope): static | Set Graph API scopes |

| getGraphScope | (): string | Current scopes |

| setPrompt | (string $prompt): static | Auth prompt type |

| setLoginHint | (string $email): static | Pre-fill email |

| setDomainHint | (string $domain): static | Domain hint |

| getUserInfo | (): array | Full Graph profile |

| getUserEmail | (): ?string | Email address |

| getUserDisplayName | (): ?string | Display name |

| getUserGivenName | (): ?string | First name |

| getUserSurname | (): ?string | Last name |

| getUserJobTitle | (): ?string | Job title |

| getUserPhoto | (): ?string | Photo binary data |

| parseIdToken | (): array | Decode JWT claims |

| validateIdToken | (): bool | Verify JWT |

| createSession | (): void | Store token in session |

| httpPost | (string $url, array $data): array | HTTP POST helper |

Office365SSO (Static Methods)

| Method | Signature | Returns |

| --- | --- | --- |

| isSessionExpired | (): bool | Token expired? |

| getSignOutUrl | (string $tenantId, ?string $postLogoutRedirectUri): string | Sign-out URL |

Inherited from OAuth2

| Method | Signature | Returns |

| --- | --- | --- |

| getAuthorizeUrl | (?string $state = null): string | Auth URL |

| requestAccessToken | (string $code): void | Exchange code |

| getAccessToken | (): ?string | Access token |

| getRefreshToken | (): ?string | Refresh token |

| getTokenExpiresAt | (): ?DateTime | Token expiry |

| refreshAccessToken | (): void | Refresh flow |

← Previous: OAuth2

Clone this wiki locally