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.

Tip: Viva Engage embedding requires your users to have valid Microsoft 365 licenses that include Viva Engage. If your organization uses Microsoft 365 Business Basic or higher, you're almost certainly covered. Confirm with your Microsoft 365 admin before you start development to avoid surprises at demo time.

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

1
Navigate to the Azure Portal App Registrations

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.

2
Fill in the Registration Details

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.

3
Create a Client Secret

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.

Warning: Never commit your client secret to version control. Store it in an environment variable or a secrets manager. If your secret is compromised, rotate it immediately in the Azure Portal and update your deployment.
4
Configure API Permissions for Viva Engage

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

1
Install Required Packages via Composer

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.

2
Create Your Environment Configuration

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
3
Create the OAuth Provider Configuration

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'          => ' ',
        ]);
    }
}
Tip: Notice the scope includes 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

1
Create the Login Redirect Handler

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;
2
Create the OAuth Callback Handler

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;
}
3
Create a Token Refresh Helper

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

1
Find Your Community's Network Permalink and Feed ID

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.

2
Build the Page that Renders the Embed

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>
Tip: The 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.
3
Create a Secure Token Proxy Endpoint (Recommended)

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;");
?>
Warning: Avoid using '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

Do I need a paid Viva Engage license to embed it, or does the standard Microsoft 365 subscription cover it?
The core Viva Engage functionality (Communities, conversations, and the Embed SDK) is included with most Microsoft 365 plans including Business Basic, Business Standard, E1, E3, and E5. The premium Viva Engage features, like analytics, answers, and leadership town halls, require the Viva suite add-on. For embedding a community feed into a PHP application, your standard Microsoft 365 subscription is sufficient. Confirm with your Microsoft 365 admin by checking Active Features in the admin center.
Can I embed Viva Engage without requiring users to log in with Microsoft credentials?
No, and this is by design. Viva Engage is an enterprise social platform built on Azure Active Directory, and all content access requires authentication. There's no public or anonymous embed mode. If you need a read-only community feed visible to unauthenticated users, you'd need to build a server-side proxy that authenticates with a service account and renders the content as static HTML. Be aware that this approach may violate Microsoft's Terms of Service depending on your use case, so review them carefully before implementing it.
The Embed SDK JavaScript file at c64.assets-yammer.com, is that URL stable, or will it change?
Microsoft has maintained this SDK URL for several years and it's referenced in the official Yammer/Viva Engage developer documentation, so it's considered stable. However, since it's a CDN-hosted file that Microsoft controls, the contents can change without notice. For production applications where you need predictability, consider self-hosting the SDK after reviewing the licensing terms, or at minimum implement a Content Security Policy and Subresource Integrity (SRI) check to detect unexpected changes.
I'm getting "AADSTS65001: The user or administrator has not consented to use the application", how do I fix this?
This error means the Yammer API permission either hasn't been granted admin consent or the user hasn't individually consented. Go to your Azure AD app registration, click API permissions, and look for the Yammer user_impersonation permission. If it shows a warning icon, admin consent hasn't been granted. Click "Grant admin consent for [your tenant]", you'll need an Azure AD admin account to do this. If individual user consent is allowed in your tenant, users will see a consent prompt on first login; this is normal behavior.
Can I embed multiple Viva Engage feeds on the same PHP page?
Yes, you can call 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.
My PHP app runs on multiple servers behind a load balancer. How should I handle sessions and tokens?
The default PHP file-based session handler doesn't work across multiple servers because session files are local to each server. You need to configure a shared session store. Redis is the most common choice for this, use the 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.
Is the Viva Engage Embed SDK available for newer frameworks, or is it only the legacy JavaScript SDK?
As of the current documentation, the Viva Engage Embed SDK remains the classic JavaScript SDK (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.