An JS-Frontend and Node.js Backend hat allows log-in via MS Azue Active Directory SSO
The whole flow is as follows
- You have to install all node modules via
npm i
- Afterwards start the node server in debug/develop mode:
nodemon server.js
- read the whole doc, setup AAD and enter the right tenant and application IDs to the code.
- tart your browser on http://localhost:3000 and you'll see something like
We're using three libs. 2 from Microsoft and 1 from npm repo
"@azure/msal-browser": "^2.21.0",
"@azure/msal-node": "^1.5.0",
"validate-azure-ad-token": "^1.0.1",
You need two infos from above
- your tenant ID for your MS AD company setup
- the application ID (aka client id) of your enterprise app (see screen 1)
const msalConfig = {
auth: {
clientId: "<<application ID>>",
authority: "https://login.microsoftonline.com/<<tenant ID>>",
redirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
}
}
Important for the understanding of the whole flow is the scopes we ask the token to be valid for... In our case it's just openid
wich is the login and nothing more.
var loginRequest = {
scopes: ["openid"]
}
The whole magic is inside the function getTokenRedirect()
If the username
var is not filled, the user is not yet logged in. Simple trick, but it does the job.
if(username=="") {
signIn()
}
see above
const tokenRequest = {
scopes: ["openid"]
}
read the username from the SSO context
tokenRequest.account = myMSALObj.getAccountByUsername(username)
try to get the token "silent". If the user is already logged in at the AAD, the user will go through a bunch of redirects but will never see a login mask...
return myMSALObj.acquireTokenSilent(tokenRequest)
.then(token => {
...
return token.accessToken
})
If that fails, you start the redirect flow where the user sees the login mask of AAD. The login (inkl. all screens) is handled by AAD.
.catch(error => {
if (error instanceof msal.InteractionRequiredAuthError) {
return myMSALObj.acquireTokenRedirect(request)
} else {
console.warn(error)
}
})
Setting the handle for the redirect promise allows you the get a token via redirect if there is no active SSO session availabe.
If there is no response
on the RedirectPromise, call getTokenRedirect()
(see above).
myMSALObj.handleRedirectPromise()
.then(resp => {
if (resp !== null) {
// you are logged :-)
console.debug(JSON.stringify(resp))
//store username and - first and foremost - token
username = resp.account.username
accessToken=resp.accessToken
accessTokenTimout=new Date(resp.expiresOn)
displayToken(resp.accessToken)
} else {
getTokenRedirect()
}
})
Adding the token to the http request you sent to the API
axios.get("http://localhost:3000/protected", {
headers: {
"Accept": "application/json",
"Authorization": "Bearer "+accessToken
},
})
On server side it's tricky as well. Especially the config for the MSAL was tricky and it took me hours to find out. At the end https://jwt.ms/ helped to descrypt the token and read the required "audience" id...
read the SSO token from the http request. As you need the value, remove the "bearer " part at the beginning.
app.get("/protected", (req, res) => {
var reqToken = (req.headers.authorization)?req.headers.authorization.split(' ')[1]:""
...
}
instanciate the lib
const validate = require('validate-azure-ad-token').default
...
Again you need your tenant and application ID. The tricy part was to find the audience ID to use. For login (= scopes: ["openid"]
) you have to use audience: '00000003-0000-0000-c000-000000000000'
validate(reqToken, {
tenantId: '<<tenant ID>>',
audience: '00000003-0000-0000-c000-000000000000',
applicationId: '<<application id>>',
scopes: ["openid"]
})
the rest is pretty straight forward. In case of success return the data. In case of an error the token is not valid (invalid in general, timed out, the user is not entitled for this app, ...)
.then(decodedToken => {
res.status(200).json({ message: "This message ..." })
})
.catch (error => {
res.status(401).json({err:"not authorised"})
})