With new Azure Portal (https://portal.azure.com/), Azure AD provides very flexible SAML-based configuration, but some folks ask me where to do that ?
In this post, I show you the answer for this question using some bit of SAML-based federation sample code of PHP and Node.js.
Note : For the settings usingย Azure Classic Portal (Azure Management Portal), see my previous posts โAzure AD Web SSO with PHP (Japanese)โ and โAzure AD Web SSO with Node.js (Japanese)โ.
Settings with Azure Portal
First of all, Iโll ย show you how the SAML settings page is improved by new Azure Portal.
When you want to register own SAML-based application, select โAzure Active Directoryโ in Azure Portal,ย click โEnterprise applicationsโ menu, and push โaddโ button.
You can select a lot of pre-defined (registered) applications (like Salesforce, Google, etc), but you click โadd your ownโ link on top of this page.
![]()
In the next screen, select โDeploying an existing applicationโ drop-down and input your app name.
http://i1155.photobucket.com/albums/p551/tsmatsuz/20170101_Set_AppName_zpsojgxd9e4.jpg
After youโve added your application, select โSingle sign-onโ menu in your app settings page and select โSAML-based Sign-onโ in โModeโ drop-down menu. (see the following screenshot)
By these steps, you can configure several SAML settings in this page.
![]()
First, you must specify your application identifier (which is used as entityID in SAML negotiation), and your appโs reply url. (Here we set โmytestappโ as identifier. We use this identifier in the following custom federation applications.)
You can also specify the relay state in this section.
![]()
In the next โattributesโ section, you can set the value of user identifier (which is returned as NameID by Azure AD in SAML negotiation), and you can also select the claims which should be returned.
When you were using Azure Classic Portal (https://manage.windowsazure.com/), you cannot specify this value and Azure AD always returned the original pairwise identifier as NameID. Some applications which need the e-mail format user principal name as NameID was used to have the trouble to federate Azure AD, but now we donโt have these kind of troubles with new Azure Portal settings.
![]()
In the next โcertificateโ section, you can create the certificate and make the rollover certificate active. Here we create this certificate and make active for the following custom code.
![]()
Custom code by PHP (simpleSAMLphp)
Now letโs start to create the code and federate with Azure AD.
First we use PHP, and here we use simpleSAMLphp for the SAML federation.
You first install IIS and PHP in your dev machine, and make sure that the following extensions are set in PHP.ini file.
extension=php_openssl.dll
extension=php_ldap.dll
Next you download simpleSAMLphp (see here), and publish {simplesamplephp install location}/www folder using IIS manager.
![]()
Remember that the page is redirected to https://{published simpleSAMLphp site}/module.php/saml/sp/saml2-acs.php/default-sp
, when the user is successfully logged-in to Azure AD with SAML federation. Then you must set this url as โReply URLโ in your app settings in Azure Portal. (see the following screenshot)
![]()
Open {simplesamplephp location}configconfig.php and change โbaseurlpathโ to your previous published url. Moreover you must change โauth.adminpasswordโ to your favorite password. (The default password value is โ123โ.)
<?php
$config = array (
. . .
'baseurlpath' => 'simplesaml/',
'certdir' => 'cert/',
'loggingdir' => 'log/',
'datadir' => 'data/',
. . .
/**
* This password must be kept secret, and modified from the default value 123.
* This password will give access to the installation page of simpleSAMLphp with
* metadata listing and diagnostics pages.
* You can also put a hash here; run "bin/pwgen.php" to generate one.
*/
'auth.adminpassword' => 'test',
'admin.protectindexpage' => false,
'admin.protectmetadata' => false,
. . .
Edit {simplesamplephp location}configauthresources.php and make sure to change entityID with the previous application identifier.
$config = array(
. . .
'default-sp' => array(
'saml:SP',
'entityID' => 'mytestapp',
'idp' => NULL,
'discoURL' => NULL
),
. . .
Next you set the federation information using simpleSAMLphp UI, and you must copy the setting information in Azure Portal beforehand.
First you must click โConfigure {your app name}โ in your app single sign-on settings page in Azure Portal.
![]()
In the configuration page, click โSAML XML Metadataโ link (see the following screenshot), and the metadata file is downloaded in your local machine. Please copy the content (text) in the downloaded file.
Note that this string content includes the digital signature by the certificate. For this reason, you shouldnโt never change this text, even if itโs space character.
http://i1155.photobucket.com/albums/p551/tsmatsuz/20170101_Download_Metadata_zpsv7qaawcz.jpg
Next you go to the simpleSAMLphp www site (in this example, https://localhost/simplesaml/index.php
) using your web browser.
In the simpleSAMLphp settings page, click โFederationโ tab and โLogin as administratorโ link. When the login screen is prompted, you enter โadminโ as user id and password which you specified above.
http://i1155.photobucket.com/albums/p551/tsmatsuz/20170101_SimpleSaml_Login_zpsx2lvg0sk.jpg
After logged-in, click โXML to simpleSAMLphp metadata converterโ link in the page (see the above screenshot), and the following metadata parser page is displayed.
Please paste your metadata which is previously copied into this textbox, and push โParseโ button. Then the converted metadata settings (which is written with PHP) is displayed in the bottom of this page. (See the following screenshot.)
Copy this PHP code, and paste into {simplesamplephp location}metadatasaml20-idp-remote.php.
![]()
<?php
...
$metadata['https://sts.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/'] = array (
'entityid' => 'https://sts.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/',
'contacts' =>
array (
),
'metadata-set' => 'saml20-idp-remote',
'SingleSignOnService' =>
array (
0 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'Location' => 'https://login.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/saml2',
),
1 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
'Location' => 'https://login.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/saml2',
),
),
'SingleLogoutService' =>
array (
0 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'Location' => 'https://login.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/saml2',
),
),
'ArtifactResolutionService' =>
array (
),
'keys' =>
array (
0 =>
array (
'encryption' => false,
'signing' => true,
'type' => 'X509Certificate',
'X509Certificate' => 'MIIC8DC...',
),
),
);
Note : On the contrary, if you want to set SAML federation SP (service provider) metadata (which includes the value of SingleLogoutService, etc) into Azure AD, you can get this XML from simpleSAMLphp and set it into Azure AD using the application manifest in Azure AD settings.
The settings of simpleSAMLphp has all done !
Letโs create your own PHP (.php) code with simpleSAMLphp like the following code. This sample code is just showing all claims returned by Azure AD.
<?php
require_once("../simplesamlphp-1.11.0/lib/_autoload.php");
$as = new SimpleSAML_Auth_Simple('default-sp');
$as->requireAuth();
$attributes = $as->getAttributes();
?>
<div style="font-weight: bold;">Hello, PHP World</div>
<table border="1">
<?php foreach ($attributes as $key => $value): ?>
<tr>
<td><?=$key;?></td>
<td><?=$value[0];?></td>
</tr>
<?php endforeach;?>
</table>
Letโs see how it works.
If you access to this PHP page with your web browser, the page is redirected to the idp selector. In this page, please select the metadata of Azure AD, and push โSelectโ button.
![]()
Then the page is redirected into the Azure AD login (sign-in) page. Please input your login id and password.
![]()
When the login is succeeded, the returned claims are shown as follows in your custom PHP page.
![]()
Custom code by Node.js (express, passport)
When you use Node.js, the concept is the same as before. You can just use your favorite SAML library with your custom code, and configure the library with the registered Azure AD app settings.
Here we use the famous passport module with express framework in Node.js.
First you start to install express framework and express command.
npm install express -g
npm install -g express-generator
Create the project directory, and provision express project by the โexpressโ command with the following commands. (The files and folders of template project are deployed, and all related packages are installed.)
After that, you can start and view the express project with your web browser. (Please run by โnpm start
โ, and access with your web browser.)
mkdir sample01
express -e sample01
cd sample01
npm install
Install passport and related modules with the following commands.
npm install express-session
npm install passport
npm install passport-saml
Open and edit app.js (the start-up js file for this express framework), and please add the following code (of bold font).
I explain about this code later.
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var session = require('express-session');
var fs = require('fs');
var SamlStrategy = require('passport-saml').Strategy;
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(new SamlStrategy(
{
path: '/login/callback',
entryPoint: 'https://login.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/saml2',
issuer: 'mytestapp',
cert: fs.readFileSync('MyTestApp.cer', 'utf-8'),
signatureAlgorithm: 'sha256'
},
function(profile, done) {
return done(null,
{
id: profile['nameID'],
email: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
displayName: profile['http://schemas.microsoft.com/identity/claims/displayname'],
firstName: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
lastName: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname']
});
})
);
var index = require('./routes/index');
var users = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session(
{
resave: true,
saveUninitialized: true,
secret: 'this shit hits'
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
app.use('/users', users);
app.get('/login',
passport.authenticate('saml', {
successRedirect: '/',
failureRedirect: '/login' })
);
app.post('/login/callback',
passport.authenticate('saml', {
failureRedirect: '/',
failureFlash: true }),
function(req, res) {
res.redirect('/');
}
);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
I explain about this sample code :
- When SAML authentication and idp redirection is needed, the entryPoint url (here,
https://login.windows.net/16d103a1-a264-4d36-9b52-51fa01ce5c2e/saml2
) is used. Please copy this value from your app configuration page in Azure AD, and paste. (see the following screenshot)
![]()
- Please see the routing code,
app.get('/login', ...);
When the user goes to /login
by the web browser, the SAML flow is proceeded and the user is redirected to the entryPoint url.
- The path
/login/callback
is the reply url. When the authentication is succeeded in the identity provider (Azure AD), the results (SAML response) is returned to this url and the claims (here nameID, emailaddress, displayname, givenname, and surname) are parsed. (see return done(null, { id: ..., email: ..., displayName:..., ... });
in the above code.)
After that, the page is redirected to /
. (Please see the routing code, app.post('/login/callback', ...);
)
Thus please set this url as reply url in Azure AD app settings beforehand.
![]()
- Please copy the X509 cert in Azure AD app settings or download the cert (see the following screenshot), and you set this cert as the passport-saml strategy.
If you set this cert, the passport-saml module validates the incoming SAML response. (The passport-saml checks if the response is not altered by the malicious code.)
![]()
Finally letโs get the returned claims and show these values in your page.
Here, we edit routes/index.js, and modify as follows. Weโre retrieving the userโs displayName and email address, and passing to the view page.
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
if(req.isAuthenticated())
res.render('index', { username: req.user.displayName, mail: req.user.email });
else
res.render('index', { username: null});
});
module.exports = router;
Edit views/index.ejs (which is the view page of the previous index.js), and modify as follows.
<!DOCTYPE html>
<html>
<head>
<title>SAML Test</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<% if (!username) { %>
<h2>Not logged in...</h2>
<% } else { %>
<h2>Hello, <%= username %>.</h2>
(your e-mail is <%=mail %>)
<% } %>
</body>
</html>
Your programming has finished !
Note : Your app must be hosted by https, and please configure to use https. (Here I donโt describe this steps.)
Please start your app using the following command.
npm start
When you access to /login using your web browser, the page is redirected and the Azure AD sign-in page is displayed.
![]()
When you succeed your login, your display name and email are displayed in the top page (index.ejs) as follows.
![]()
ย
If youโre ISV folks, you can submit your own custom app (which is federated with Azure AD) to Azure AD gallery. Everyone can start and use your app (ISV app) federated with Azure AD with a few clicks !