Microsoft Entra ID - Admin SSO Provider
- Properly configure Strapi for SSO
- Create your EntraID OAuth2 app by following the steps in the EntraID/Azure Portal.
- It's important to review the OAuth application types as Strapi only supports "web" applications.
- Gather the required information to set as environment variables in your Strapi project:
- MICROSOFT_CLIENT_ID
- MICROSOFT_CLIENT_SECRET
- MICROSOFT_TENANT_ID
Required configuration before setting up SSO
Server Configuration
The following server configurations are required when using SSO, for more information on available options please see the Server Configuration documentation.
url
: The public facing URL of your Strapi application. (e.g.https://api.example.com
)proxy.koa
: Enabling trusted reverse proxy support. (true
)
Admin Required Configuration Example
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
// ...
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: env.bool('TRUST_PROXY', true),
},
// ...
});
export default ({ env }) => ({
// ...
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: env.bool('TRUST_PROXY', true),
},
// ...
});
There are also some optional configurations that you can set should it be necessary:
proxy.global
: If you are in a restricted network environment that requires a forward proxy (e.g Squid) for all outgoing requests. (e.g.http://username:password@yourProxy:3128
)
Admin Optional Configuration Example
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
// ...
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: env.bool('TRUST_PROXY', true),
global: env('GLOBAL_PROXY'),
},
// ...
});
export default ({ env }) => ({
// ...
url: env('PUBLIC_URL', 'https://api.example.com'),
proxy: {
koa: env.bool('TRUST_PROXY', true),
global: env('GLOBAL_PROXY'),
},
// ...
});
Admin Configuration
There are some optional configurations that you can set should it be necessary, for more information on available options please see the Admin Configuration documentation.
url
: The public facing URL of your Strapi administration panel. (e.g.https://admin.example.com
)auth.domain
: Setting a custom domain for cookie storage. (e.g..example.com
)
When deploying the admin panel to a different location or on a different subdomain, an additional configuration is required to set the common domain for the cookies. This is required to ensure the cookies are shared across the domains.
Deploying the admin and backend on entirely different unrelated domains is not possible at this time when using SSO due to restrictions in cross-domain cookies.
Admin Optional Configuration Example
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
// ...
url: env('PUBLIC_ADMIN_URL', 'https://admin.example.com'),
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".example.com"),
providers: [
// ...
],
},
// ...
});
export default ({ env }) => ({
// ...
url: env('PUBLIC_ADMIN_URL', 'https://admin.example.com'),
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".example.com"),
providers: [
// ...
],
},
// ...
});
Middlewares Configuration
The following middleware configurations are required when using SSO, for more information on available options please see the Middlewares Configuration documentation.
contentSecurityPolicy
: Allows you to configure the Content Security Policy (CSP) for your Strapi application. This is used to prevent cross-site scripting attacks by allowing you to control what resources can be loaded by your application.
By default, Strapi security policy does not allow loading images from external URLs, so provider logos will not show up on the login screen of the admin panel unless a security exception is added or you use a file uploaded directly on your Strapi application.
Middlewares Configuration Example
- JavaScript
- TypeScript
module.exports = [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'cdn2.iconfinder.com', // Base URL of the provider's logo without the protocol
],
'media-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'cdn2.iconfinder.com', // Base URL of the provider's logo without the protocol
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
export default [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'cdn2.iconfinder.com', // Base URL of the provider's logo without the protocol
],
'media-src': [
"'self'",
'data:',
'blob:',
'market-assets.strapi.io',
'cdn2.iconfinder.com', // Base URL of the provider's logo without the protocol
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
Provider Specific Notes
Scopes
The EntraID OAuth2 provider requires the following scopes, however additional scopes can be added as needed depending on your use case and the data you need returned:
Profile Data
It is extremely likely that the below example will not work directly for you as the fields returned by the EntraID instance are extremely subjective to each individual setup. For example some instances will have a upn
field, others will not and the value type of the upn
may be different for each instance or even between different users in the same instance.
Data returned from the provider is dependent on how your EntraID OAuth2 application is configured. The example below assumes that the EntraID OAuth2 application is configured to return the user's email, first name, and last name. Fields returned by the provider can change based on the scopes requested and the user's EntraID account settings.
If you aren't sure what data is being returned by the provider, you can log the waadProfile
object in the createStrategy
function to see what data is available as seen in the following example.
Configuration Example with Logging
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
// See what is returned by the provider
console.log(waadProfile);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
Redirect URL/URI
The redirect URL/URI will be dependent on your provider configuration however in most cases should combine your application's public URL and the provider's callback URL. The example below shows how to combine the public URL with the provider's callback URL.
callbackURL:
env('PUBLIC_URL', "https://api.example.com") +
strapi.admin.services.passport.getStrategyCallbackURL("azure_ad_oauth2"),
In this example the redirect URL/URI used by the provider will be https://api.example.com/admin/connect/azure_ad_oauth2
.
This is broken down as follows:
https://api.example.com
is the public URL of your Strapi application/admin/connect
is the general path for SSO callbacks in Strapi/azure_ad_oauth2
is the specific provider UID for Mircosoft Entra ID / Azure Active Directory
Strapi Configuration
Using: passport-azure-ad-oauth2
Install the Provider Package
- yarn
- npm
yarn add passport-azure-ad-oauth2 jsonwebtoken
npm install --save passport-azure-ad-oauth2 jsonwebtoken
Adding the Provider to Strapi
- JavaScript
- TypeScript
const AzureAdOAuth2Strategy = require("passport-azure-ad-oauth2");
const jwt = require("jsonwebtoken");
module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (strapi) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
env('PUBLIC_URL') +
strapi.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
),
},
],
},
});
import { Strategy as AzureAdOAuth2Strategy} from "passport-azure-ad-oauth2";
import jwt from "jsonwebtoken";
export default ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (strapi) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
env('PUBLIC_URL') +
strapi.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
),
},
],
},
});