•   over 4 years ago

Getting Started with Graph Security API and Serverless

We have been trying out the Graph API at ZEIT in an serverless environment using Node.js and we really think it’s a perfect fit. We know a few things about running microservices and serverless applications at scale but we are quite new to the Graph API. I wanted to share some our findings we have made so far, to help help everybody to get started with the API and Node.js. Our application is deployed completely as a single Now deployment utilizing both static files and Node.js based Now lambdas.

To get an Azure AD up and running was an extremely easy task and it’s well covered elsewhere. Personally I found the 30DaysMSGraph blog post series to be very helpful in understanding the basics of the whole API family. In the Day 9 post they explain how to register your new application with the AAD: https://developer.microsoft.com/en-us/graph/blogs/30daysmsgraph-day-9-azure-ad-applications-on-v2-endpoint/

After you have your application registered you can access anything that the Graph API provides given that you give an user the required permissions. Here we are of course mostly interested in the security part of the API.

The Graph API client for Node.js is called `@microsoft/microsoft-graph-client` and it’s absolutely great in terms of learning curve and its interface. The way querying and filtering is implemented seems just right and it returns promises, which is the way we like to work with js. The only issue we have found with the library is that it’s not isomorphic out of the box (but it provides a separate browser version). Therefore we couldn’t easily create an SSR SPA that would make Graph API requests both on the server side as well as the client side using the same authentication token.

We decided to use `passport` and `passport-azure-ad` with `express` to authenticate the user to use the Graph API, as those also used by the official example and it’s a well tested solution. By default `passport` utilizes sessions for validation, but since we wanted to make a serverless application, we couldn’t accept sessions because that would require a database connection to store the session state. That’s because the lifetime of a process and its memory content in a typical lambda/function serverless environment might be just over the function call. Luckily `passport-azure-ad` supports encrypted cookies as an alternative. So our AAD auth config looks like follows.

```
module.exports = {
creds: {
redirectUrl: 'https://DOMAIN/token',
clientID: process.env.GRAPH_CLIENT_ID,
clientSecret: process.env.GRAPH_CLIENT_SECRET,
identityMetadata: process.env.GRAPH_METADATA,
allowHttpForRedirectUrl: false,
responseType: 'code',
validateIssuer: true,
responseMode: 'query',
scope: ['User.Read', 'Profile'],
useCookieInsteadOfSession: true,
cookieEncryptionKeys: [{
key: process.env.GRAPH_COOKIE_KEY,
iv: process.env.GRAPH_COOKIE_IV
}]
}
};
```

Once logged in we’ll receive a bearer token sent to `/token` path and we’ll store it in another cookie. That cookie will be then passed back to each API call automatically by the browser and further on we can pass it directly to the Graph API calls. Since we are not making an isomorphic application the cookie can be set as httpOnly.

Creating a separate middleware API for an application isn’t necessarily bad as it allows you to apply your own rate limiting or perhaps access to other systems that you couldn’t access from a browser. Note that the authentication middleware can’t be implemented completely on the client side anyway because you need to use the Graph API secret that should not be shared with the user. But the application using the Graph API could be, in theory, running completely on an SSR and/or static framework like Next.js.

While we used `express` for the authentication workflow, we didn’t actually use it for the other API endpoints but instead we used just plain node.js functions. How? We used Now’s `@now/node` runtime that basically just calls the function you have exported like it was a standard HTTP request callback. Therefore the other API endpoints can be very efficient and fast as something else outside of our scope is taking care of the actual networking part, and we can concentrate on the business logic.

We created a neat wrapper that we can use to get a Graph API client on every API invocation:

```
const MicrosoftGraph = require("@microsoft/microsoft-graph-client");
const Cookies = require('cookies');
const { APIVERSION } = require('./consts');

module.exports = function getGraphClient(req, res) {
const cookies = new Cookies(req, res);
const token = cookies.get('token');
if (!token) {
return null;
}

return MicrosoftGraph.Client.init({
defaultVersion: APIVERSION,
debugLogging: true,
authProvider: (done) => {
//first parameter takes an error if you can't get an access token
done(null, token);
}
});
};
```

Then we use that as follows:

```
module.exports = (req, res) => {
if (methods[req.method]) {
const client = getGraphClient(req, res);

if (!client) {
res.writeHead(401);
res.end();
return;
}
```

Just to demonstrate how simple it really was to get to this point, here is the `now.json` definition file for our first serverless test deployment using the Graph Security API:

```
{
"version": 2,
"name": "graph-security-test",
"builds": [
{ "src": "/api/endpoints/**/*.js", "use": "@now/node" },
{ "src": "/site/package.json", "use": "@now/next" }
],
"routes": [
{ "src": "/login", "dest": "/api/endpoints/login" },
{ "src": "/token", "dest": "/api/endpoints/token" },
{ "src": "/api/(.*)", "dest": "/api/endpoints/$1" },
{ "src": "/(.*)", "dest": "/site/$1" }
],
"env": {
"GRAPH_CLIENT_ID": "@graph-client-id",
"GRAPH_CLIENT_SECRET": "@graph-secret-test",
"GRAPH_METADATA": "@graph-metadata",
"GRAPH_COOKIE_KEY": "@graph-cookie-key",
"GRAPH_COOKIE_IV": "NOT_SO_SECRET"
},
"alias": "graph.DOMAIN"
}
```

This is practically just a handful of single file cloud functions deployed to the cloud. `/api` hosts all the API code running on the server side and `/site` hosts the Next.js web application that renders the user interface.

I will be sharing more examples and tips while we get forward with our experiments with the Graph Security API. Also feel free to ask anything regarding best practices with serverless applications.

  • 2 comments

  •   •   over 4 years ago

    I didn't cover logout in my last post. That part is actually very simple now that we decided to not use sessions with Passport.

    Normally you would call `req.logout();` with Passport but now instead of doing that you can simply remove the cookies and make a logout request to the Microsoft Online OAuth endpoint. The logout workflow requires a logout URL that also needs to be configured in AAD for the application similar to the login token URL.

    Now you have two options here, in theory the logout URL should be the actual endpoint clearing the session but it can be also configured to be a sort of bogus URL, for example the index page URL. If you decide to follow the intended workflow you'll need to either redirect to the OAuth logout URL from your API or have the client request that URL directly, and then finally your logout endpoint would clear the session. The other option is to clear the

    To keep the API symmetric in our example we decided to just have a `/logout` endpoint that is directly called by the client. The endpoint first clears the auth cookies, then redirects to the OAuth logout URL, and finally the user is redirected to the front page.

    ```
    app.get('*', (req, res) => {
    res.clearCookie('token');
    res.clearCookie('user');
    res.redirect(`https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=${logoutRedirectUrl}`);
    });
    ```

    The only downside of this approach is probably that if something else in the front-end would trigger the OAuth logout then the front won't be immediately aware of the logout.

    We have now posted a fully working example here
    https://github.com/zeit/now-examples/tree/master/nodejs-ms-graph-security-api

    The example covers login, logout, and fetching security events. It also provides a good template for developing a Next.js based console using the Microsoft Graph Security API.

  •   •   over 4 years ago

    We decided to use `passport` and` passport-azure-ad`` with `express` to authenticate the user in Graph API, just like those used by the official example, and it is a well-tested solution. By default, `passport` uses sessions for validation, but since we wanted to create an application without a server, we could not accept sessions because it would require a connection to the database to store the state of the session. This is because the lifetime of a process and its contents in memory in a typical serverless / non-lambda environment can be just above the function call. Fortunately, "passport-blue-ad" supports encrypted cookies. https://www.prepacademia.com/ged.html

Comments are closed.