If you've been tasked with bringing Microsoft Viva Engage (formerly Yammer) into your PHP-based web application, you already know the documentation is scattered, the authentication flow is finicky, and the official SDK examples assume you're working inside a SharePoint or Teams environment. You're not, you're working in PHP, and that changes everything. This guide walks you through the entire integration end-to-end, from registering your Azure AD app to rendering a fully authenticated Viva Engage feed inside your own web pages.
By the end of this guide, you'll have a working PHP implementation that embeds a Viva Engage community feed, handles OAuth 2.0 token exchange, and degrades gracefully when users aren't authenticated. Whether you're building an internal intranet portal, a partner extranet, or a public-facing application that also happens to need enterprise social features, this is the path forward.
What Is Viva Engage and Why Would You Embed It?
Viva Engage is Microsoft's enterprise social networking layer, the engine that powers community conversations, town halls, and knowledge-sharing across Microsoft 365. It sits on top of the Yammer infrastructure and is deeply tied into Azure Active Directory (Azure AD, now called Microsoft Entra ID) for identity.
Organizations embed Viva Engage in custom applications for several reasons. You might want your company intranet, built on a PHP framework like Laravel or Symfony, to surface the latest community conversations without forcing users to context-switch into a separate browser tab. You might be building a customer portal where your internal teams need to see community updates alongside customer data. Or you might be modernizing a legacy CMS and want to add social collaboration features without rebuilding everything from scratch.
The embedding approach uses a combination of Microsoft's OAuth 2.0 flow for authentication, the Viva Engage Embed JavaScript SDK, and, on the server side, your PHP application acting as a secure token broker. That last part is the piece most tutorials gloss over, and it's exactly where most PHP developers get stuck.
Why This Integration Is More Complex Than a Simple Iframe
You might be tempted to just drop an iframe pointing at the Viva Engage web app. That approach breaks in almost every real scenario because of two things: X-Frame-Options headers and cookie-based authentication.
Microsoft sets restrictive X-Frame-Options and Content-Security-Policy headers on the main Viva Engage web application, which means most browsers will refuse to display it inside a cross-origin iframe. Even if a user is already logged into Viva Engage in another tab, the iframe context isolates cookies in modern browsers (thanks to SameSite cookie policies introduced in Chrome 80+), so the embed will show a login prompt instead of content.
The proper solution is to use the Viva Engage Embed SDK, a JavaScript library Microsoft provides specifically for this use case. The SDK communicates with Viva Engage through postMessage rather than a raw iframe load, and it handles the authentication handshake through a popup or redirect flow that your PHP backend facilitates. Your PHP application needs to:
- Register an application in Azure AD and obtain OAuth credentials
- Implement the OAuth 2.0 authorization code flow to obtain an access token on behalf of the signed-in user
- Pass that token to the Viva Engage Embed SDK securely
- Render the embed container and initialize the SDK
Let's go through each of these steps in detail.
Prerequisites
Before you write a single line of PHP, make sure you have the following in place:
- Azure AD / Microsoft Entra ID access: You need permission to register applications in your organization's Azure AD tenant. If you don't have this, you'll need to work with your Azure administrator.
- Microsoft 365 tenant with Viva Engage enabled: Your tenant must have Viva Engage (Yammer) activated. You can verify this in the Microsoft 365 admin center under Active features.
- PHP 8.0 or higher: The examples in this guide use modern PHP syntax. If you're on PHP 7.x, most of the code still works but you'll want to double-check type declarations.
- Composer: We'll use the league/oauth2-client package to handle the OAuth flow cleanly. You can do this with raw cURL if you prefer, but the library saves substantial boilerplate.
- HTTPS on your development and production environments: OAuth 2.0 redirect URIs must use HTTPS in production. For local development, you can use HTTP with localhost as a special exception in Azure AD.
- A Viva Engage community to embed: You'll need the network permalink and community ID. We'll cover how to find these later.
Step 1: Register Your Application in Azure AD
Sign in to the Azure Portal and search for "App registrations" in the top search bar. Click on App registrations under Services, then click New registration.
Give your application a descriptive name, something like "MyIntranet Viva Engage Integration". Under Supported account types, choose Accounts in this organizational directory only (Single tenant) unless you're building a multi-tenant app for multiple organizations. For the Redirect URI, select Web and enter the callback URL your PHP application will handle, for example:
https://yourdomain.com/auth/microsoft/callback
For local development, add:
http://localhost:8080/auth/microsoft/callback
Click Register. Azure will create the app and take you to its overview page. Copy the Application (client) ID and Directory (tenant) ID, you'll need both.
In the left sidebar, click Certificates & secrets, then New client secret. Give it a description and choose an expiry period. After you click Add, the secret value is shown once. Copy it immediately and store it securely, you cannot retrieve it again after navigating away.
Click API permissions in the left sidebar, then Add a permission. In the panel that opens, scroll down and select Yammer (this is the underlying API for Viva Engage). Under Delegated permissions, check:
- user_impersonation, allows the app to access Yammer/Viva Engage as the signed-in user
Click Add permissions. If your organization requires admin consent for delegated permissions (common in enterprise environments), click Grant admin consent for [your tenant] and confirm. Without this step, users may see a consent screen they can't dismiss.
Step 2: Set Up Your PHP Project and Dependencies
In your project root, run the following to install the OAuth2 client library and a dotenv loader for managing your credentials:
composer require league/oauth2-client vlucas/phpdotenv
If you're using Laravel, the OAuth client is already available and you can use Laravel Socialite or Passport instead, but this guide covers the framework-agnostic approach that works with any PHP application.
Create a .env file in your project root (and add it to .gitignore immediately):
AZURE_CLIENT_ID=your-application-client-id-here
AZURE_CLIENT_SECRET=your-client-secret-here
AZURE_TENANT_ID=your-directory-tenant-id-here
AZURE_REDIRECT_URI=https://yourdomain.com/auth/microsoft/callback
APP_SESSION_SECRET=a-long-random-string-for-session-signing
VIVA_ENGAGE_NETWORK=yourcompany.onmicrosoft.com
Create a file at src/Auth/MicrosoftProvider.php. The league/oauth2-client package doesn't include a Microsoft-specific provider by default, so we'll configure the authorization and token endpoints manually:
<?php
namespace App\Auth;
use League\OAuth2\Client\Provider\GenericProvider;
use Dotenv\Dotenv;
class MicrosoftProvider
{
public static function create(): GenericProvider
{
$tenantId = $_ENV['AZURE_TENANT_ID'];
return new GenericProvider([
'clientId' => $_ENV['AZURE_CLIENT_ID'],
'clientSecret' => $_ENV['AZURE_CLIENT_SECRET'],
'redirectUri' => $_ENV['AZURE_REDIRECT_URI'],
'urlAuthorize' => "https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/authorize",
'urlAccessToken' => "https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/token",
'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me',
'scopes' => 'openid profile email https://api.yammer.com/user_impersonation',
'scopeSeparator' => ' ',
]);
}
}
https://api.yammer.com/user_impersonation. This is the exact scope string required for Viva Engage API access. Using just user_impersonation without the full URI will cause the token request to fail silently or return a token without Yammer claims.
Step 3: Implement the OAuth 2.0 Authorization Flow
Create auth/login.php. This file redirects the user to Microsoft's login page:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
session_start();
$provider = App\Auth\MicrosoftProvider::create();
// Generate and store CSRF state token
$state = bin2hex(random_bytes(16));
$_SESSION['oauth2_state'] = $state;
$authorizationUrl = $provider->getAuthorizationUrl([
'state' => $state,
'response_type' => 'code',
]);
header('Location: ' . $authorizationUrl);
exit;
Create auth/callback.php. This handles the redirect back from Microsoft and exchanges the authorization code for an access token:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
session_start();
// CSRF state validation
if (empty($_GET['state']) || $_GET['state'] !== $_SESSION['oauth2_state']) {
unset($_SESSION['oauth2_state']);
http_response_code(403);
echo 'Invalid OAuth state. Please try logging in again.';
exit;
}
if (!empty($_GET['error'])) {
$error = htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8');
$description = htmlspecialchars($_GET['error_description'] ?? '', ENT_QUOTES, 'UTF-8');
echo "Authorization error: {$error}, {$description}";
exit;
}
$provider = App\Auth\MicrosoftProvider::create();
try {
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code'],
]);
// Store token data in session
$_SESSION['viva_access_token'] = $accessToken->getToken();
$_SESSION['viva_token_expires'] = $accessToken->getExpires();
$_SESSION['viva_refresh_token'] = $accessToken->getRefreshToken();
// Redirect to the page that initiated the login
$returnTo = $_SESSION['return_to'] ?? '/';
unset($_SESSION['return_to']);
header('Location: ' . $returnTo);
exit;
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
error_log('OAuth token exchange failed: ' . $e->getMessage());
echo 'Authentication failed. Please try again.';
exit;
}
Access tokens expire. Create src/Auth/TokenManager.php to handle automatic refresh:
<?php
namespace App\Auth;
class TokenManager
{
public static function getValidToken(): ?string
{
if (empty($_SESSION['viva_access_token'])) {
return null;
}
// If token expires within 5 minutes, refresh it
$buffer = 300;
if (!empty($_SESSION['viva_token_expires']) &&
$_SESSION['viva_token_expires'] - $buffer < time()) {
if (empty($_SESSION['viva_refresh_token'])) {
return null;
}
$provider = MicrosoftProvider::create();
try {
$newToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $_SESSION['viva_refresh_token'],
]);
$_SESSION['viva_access_token'] = $newToken->getToken();
$_SESSION['viva_token_expires'] = $newToken->getExpires();
$_SESSION['viva_refresh_token'] = $newToken->getRefreshToken();
} catch (\Exception $e) {
// Refresh failed, user needs to log in again
unset(
$_SESSION['viva_access_token'],
$_SESSION['viva_token_expires'],
$_SESSION['viva_refresh_token']
);
return null;
}
}
return $_SESSION['viva_access_token'];
}
}
Step 4: Embed Viva Engage in Your PHP Page
Navigate to the Viva Engage community you want to embed in a browser. Look at the URL, it will be structured like:
https://web.yammer.com/main/org/yourcompany.onmicrosoft.com/groups/eyJfdHlwZSI6Ikdyb3VwIiwiaWQiOiIxMjM0NTY3In0/all
The encoded segment after /groups/ is the group/community ID. You can also use the Viva Engage API to retrieve it programmatically. For the Embed SDK, you'll primarily need the feedType (group, topic, user, or open-graph) and the feedId.
To get the numeric group ID, call the Yammer API with your access token:
GET https://www.yammer.com/api/v1/groups.json
Authorization: Bearer {your_access_token}
Each group object in the response has an id field, that's your feedId.
Create pages/community.php. This page checks for a valid token, initiates login if needed, then renders the Viva Engage embed:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
session_start();
use App\Auth\TokenManager;
$token = TokenManager::getValidToken();
if ($token === null) {
// Store the current URL so we can return here after login
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
header('Location: /auth/login.php');
exit;
}
// Pass token to JavaScript safely, never expose full token in page source
// in a real application; use a server-side proxy endpoint instead.
// For this example, we're encoding it for the SDK initialization.
$safeToken = htmlspecialchars($token, ENT_QUOTES, 'UTF-8');
$feedId = '1234567'; // Replace with your actual community ID
$networkPermalink = htmlspecialchars($_ENV['VIVA_ENGAGE_NETWORK'], ENT_QUOTES, 'UTF-8');
?>
<!-- Include the Viva Engage Embed SDK -->
<script type="text/javascript" src="https://c64.assets-yammer.com/assets/platform_embed.js"></script>
<div id="viva-engage-container" style="width: 100%; min-height: 400px;"></div>
<script>
(function() {
yam.config({
network: '<?php echo $networkPermalink; ?>',
appClientId: '<?php echo htmlspecialchars($_ENV['AZURE_CLIENT_ID'], ENT_QUOTES, 'UTF-8'); ?>',
});
yam.connect.loginWithRedirect(
function(resp) {
// Login successful, render the feed
yam.render({
container: '#viva-engage-container',
network: '<?php echo $networkPermalink; ?>',
appClientId: '<?php echo htmlspecialchars($_ENV['AZURE_CLIENT_ID'], ENT_QUOTES, 'UTF-8'); ?>',
feedType: 'group',
feedId: '<?php echo htmlspecialchars($feedId, ENT_QUOTES, 'UTF-8'); ?>',
config: {
use_sso: false,
header: false,
footer: false,
showOpenGraphPreview: false,
defaultToCanonical: false,
hideNetworkName: true,
promptText: 'Start a conversation...'
}
});
},
function(resp) {
// Login failed or user cancelled
document.getElementById('viva-engage-container').innerHTML =
'<p>Please sign in to view community discussions.</p>';
}
);
})();
</script>
yam.render() function supports several feedType values: group for a community feed, topic for a hashtag feed, user for a user's profile feed, open-graph for page-specific conversations, and my-feed for the user's personal home feed. Choose the one that matches your use case.
Embedding the access token directly in page HTML is acceptable for internal apps but not ideal for production. Instead, create a server-side endpoint that the Embed SDK can use to silently refresh authentication. Create api/token.php:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
session_start();
use App\Auth\TokenManager;
header('Content-Type: application/json');
header('Cache-Control: no-store');
$token = TokenManager::getValidToken();
if ($token === null) {
http_response_code(401);
echo json_encode(['error' => 'not_authenticated']);
exit;
}
// Return only what the SDK needs, never return the full token to untrusted clients
echo json_encode([
'authenticated' => true,
'token_hint' => substr($token, 0, 10) . '...' // for debugging only
]);
Advanced Troubleshooting
The Embed Shows a Blank Box or a Login Prompt
This is the most common issue. Open your browser's developer tools and look at the Network tab. If you see requests to api.yammer.com returning 401, your token isn't being passed correctly to the SDK. Check that your appClientId matches the Application Client ID in Azure AD exactly, and confirm the Yammer API permission was granted admin consent.
If requests return 403, the user's account may not be provisioned in Viva Engage. Users must have signed in to Viva Engage at least once before the embed will work for them. Have your Microsoft 365 admin check the user's Viva Engage activation status.
CORS Errors in the Browser Console
If you see Access-Control-Allow-Origin errors, your domain may not be registered as an allowed origin for the Viva Engage API. Ensure your Azure AD app's redirect URI is correctly configured and that you're loading the embed from an HTTPS URL in production. Note that localhost is explicitly allowed for development, but custom local domain names (like myapp.local) may not be.
The SDK Loads but the feedId Seems Wrong
The feedId for a group must be the numeric ID, not the encoded string from the URL. Use the Yammer groups API endpoint to retrieve the correct ID. You can also find it by right-clicking on the group name in the Viva Engage web app and inspecting the link element, the data attribute often contains the numeric ID.
Token Refresh Fails Silently
If users are getting logged out unexpectedly, check that your PHP session lifetime is configured appropriately in your php.ini or session configuration. The default PHP session lifetime (1440 seconds) is much shorter than OAuth token lifetimes (typically 1 hour). Set session.gc_maxlifetime to at least 3600 to avoid session expiry causing premature logouts.
Multiple Tenants or Guest Users Failing
If your application needs to support guest users (B2B collaboration), change your Azure AD app's supported account type to Accounts in any organizational directory. Guest users must be Viva Engage-licensed within your tenant, not just their home tenant. Work with your Azure admin to ensure guests are provisioned correctly.
Content Security Policy Blocking the Embed SDK
If your application has a strict CSP, you'll need to allow the Viva Engage embed assets. Add these to your CSP headers:
Content-Security-Policy:
script-src 'self' https://c64.assets-yammer.com https://*.yammer.com;
frame-src https://*.yammer.com https://*.microsoft.com;
connect-src 'self' https://api.yammer.com https://*.yammer.com;
img-src 'self' https://*.yammer.com https://*.microsoft.com data:;
In PHP, add these headers before any output:
<?php
header("Content-Security-Policy: script-src 'self' https://c64.assets-yammer.com https://*.yammer.com; frame-src https://*.yammer.com https://*.microsoft.com; connect-src 'self' https://api.yammer.com https://*.yammer.com;");
?>
'unsafe-inline' in your CSP to work around SDK loading issues. That defeats the purpose of having a CSP. Instead, use a nonce-based approach for inline scripts if you need them alongside the Viva Engage SDK.
Prevention and Best Practices
Rotate Client Secrets Before They Expire
Azure AD client secrets expire. If yours expires in production, users will suddenly be unable to authenticate. Set a calendar reminder 30 days before your secret's expiry date. The Azure Portal shows expiry dates in the Certificates & secrets section. When rotating, create the new secret first, update your application's environment variables, deploy, and then delete the old secret.
Store Tokens Server-Side Only
Never store OAuth access tokens in localStorage or cookies visible to JavaScript. Keep them in your PHP session store (backed by Redis or a database in production, not the filesystem in a multi-server environment). If you must pass authentication state to JavaScript, use short-lived session cookies with the HttpOnly and SameSite=Strict flags.
Implement PKCE for Enhanced Security
The Proof Key for Code Exchange (PKCE) extension adds an extra layer of protection against authorization code interception attacks. The league/oauth2-client package supports PKCE, add it to your authorization URL generation:
$pkceCode = $provider->getAuthorizationUrl([
'state' => $state,
'code_challenge_method' => 'S256',
]);
$_SESSION['pkce_verifier'] = $provider->getPkceCode();
Monitor Token Usage with Azure AD Sign-In Logs
In the Azure Portal under Azure Active Directory → Sign-in logs, you can monitor authentication activity for your app. Set up alerts for unusual sign-in patterns, high failure rates, or sign-ins from unexpected locations. This is especially important if your PHP application is publicly accessible.
Handle the Embed Gracefully When Viva Engage Is Unavailable
Viva Engage, like any cloud service, has occasional outages. Wrap your embed in a try/catch at the JavaScript level and provide a fallback UI:
window.addEventListener('yam:error', function(e) {
document.getElementById('viva-engage-container').innerHTML =
'<div class="callout callout-warning">' +
'Community feed is temporarily unavailable. ' +
'<a href="https://web.yammer.com" target="_blank">Open Viva Engage directly</a>.' +
'</div>';
});
Frequently Asked Questions
yam.render() multiple times with different container elements and different feedId values. Each call creates an independent embed. The SDK handles multiple instances gracefully as long as each has a unique container selector. Be mindful of performance, each embed makes its own API calls to Viva Engage, so embedding three or more feeds on a single page can noticeably slow page load times. Consider lazy-loading feeds that are below the fold.predis/predis package and configure PHP to use a Redis session handler. Set your session save handler in php.ini or at runtime: ini_set('session.save_handler', 'redis') and ini_set('session.save_path', 'tcp://your-redis-host:6379'). This ensures that regardless of which server handles a request, the OAuth tokens stored in the session are available.platform_embed.js). Microsoft has been investing heavily in the full Viva Engage web experience and the Microsoft Graph API rather than the embed SDK. If you need deeper integration, for example, posting messages programmatically, retrieving user data, or building custom UI on top of Viva Engage data, consider using the Microsoft Graph API directly (specifically the /communities endpoints) rather than the embed SDK. The Graph API approach gives you more control but requires building your own UI components.