One of the first questions we’re asked is usually about authentication and authorization in a serverless environment. Without a server, how does one authenticate users and secure access to resources? To help answer these questions, we introduce an AWS service called Cognito and another (non-AWS) service called Auth0. We also introduce the AWS API Gateway and show how to use it to create an API. We show you how to secure this API using custom authorizers and connect it to Lambda functions. Lastly, we show how to extend 24-Hour Video to provide sign-in, sign-out, and user-profile facilities by combining features of Auth0, API Gateway, and Lambda.
In modern web and mobile applications, authentication and authorization can take a number of forms. Allowing users to directly sign up with the application or sign in via an enterprise directory is important. It can be equally important to allow users to authenticate with a third-party identity provider (IdP) such as Google, Facebook, or Twitter. You might ask how one implements and manages all the required authentication, authorization, user sign-up, and user validation concerns without a server. The answer is by using services such as AWS Cognito and Auth0 and technologies such as delegation tokens. Before we discuss these services and technologies in more detail, you may want to look at appendix C. This appendix serves as a nice refresher on the topics of authentication and authorization, OpenID, and OAuth 2.0.
Authenticating a user and then authorizing access to needed services may seem like a challenge without a server, but it isn’t difficult once you understand what’s possible:
Figure 5.1 shows what a possible authentication and authorization architecture may look like in a serverless application. The process of authentication is managed using Auth0, which takes care of authentication and creation of delegation tokens needed for direct authentication with other services. As you can see in the figure, the client can access the database directly or send requests to a Lambda function that can access the database using its own credentials. You have flexibility in choosing the best approach for your system.
Throughout this chapter we’ll refer to JWT, which stands for JSON Web Token. The Internet Engineering Task Force (IETF) describes JWT as a “compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted” (http://bit.ly/1Spxog6). See the section on JWT in appendix C to learn more.
In chapter 1, we told you that in serverless architecture “the presentation tier of the application communicates directly with services, the database, or compute functions via an API gateway. Many services can be accessed directly by the front end. Some services need to be hidden behind compute service functions where additional security measures and validation can take place.” This description stands in contrast to many traditional systems where communication often flows through a back end that coordinates access to the database and services. So when designing your serverless system for authentication and authorization, remember the following points:
Later in the book, in chapter 9, we’re going to show how to use a JWT-based delegation token to authorize access to your database and to other services. JWT is great, but it’s not supported by all services everywhere. There will be times when you’ll have to use signatures or temporary credentials instead. In this chapter (and beyond) assume that delegation tokens are JSON Web Tokens. But if we use other ways of granting temporary access to services, we’ll clearly mention it.
When building your serverless architecture, try to reduce the number of steps your system has to take to perform an action. Allow your front end to communicate with services directly if it’s secure and appropriate to do so. This will reduce latency and make the system easier to manage.
Furthermore, don’t come up with your own way of performing authentication and authorization. Try to adopt common protocols and specifications. You’re likely to integrate with multiple third-party services and APIs that implement these as well. Security is difficult, so if you follow tried-and-tested models for authentication and authorization, you’re more likely to succeed.
As a developer, you can build your own authentication and authorization system if you wish to do so. OpenID Connect and OAuth 2.0 can help you support external identity providers. Add a Lambda function, a database, and a sign-up/sign-in page, and you can begin to authenticate users. But why build when someone else might have already done it? Let’s look at existing services to see if they can reduce the amount of work you would normally have to do.
Amazon Cognito (https://aws.amazon.com/cognito) is a service from Amazon that can help with authentication. You can use it to build an entire registration and login system, and it can integrate with public identity providers or your own (existing) authentication process.
Authenticated and unauthenticated users going through Cognito are assigned an IAM role/temporary credentials. This allows users to access resources and services in AWS. Cognito can also save end-user data. This data can be synced and accessed across different devices. Figure 5.2 shows how a user can authenticate with an identity provider and then get access to a database in AWS. Cognito acts as an intermediary (see http://amzn.to/1SmsmPt for more information on Cognito authentication flows).
Cognito is a great service but it has a number of limitations. Useful features such as password reset require a bit of manual implementation and don’t have some of the more advanced features such as log on via TouchID. Cognito is a great system but there’s another alternative we should explore: a service called Auth0.
Auth0 (https://auth0.com) can be labeled a universal identity platform. It supports custom user sign-up/sign-in with a username and a password, integrates with identity providers that use OAuth 2.0 and OAuth 1.0, and connects to enterprise directories. It also has advanced features such as multi-factor authentication and TouchID support.
When a user authenticates with Auth0, the client application receives a JSON Web Token. This token can be used in a Lambda function if it needs to identify the user, or it can be used to request a delegation token (from Auth0) for another service. Auth0 integrates well with AWS. It can obtain temporary AWS credentials to securely access AWS resources, so you don’t lose anything by using Auth0 instead of Cognito (for more information about integration with AWS see https://auth0.com/docs/integrations/aws).
Cognito and Auth0 are both very capable systems. You should explore the unique features they offer and make an assessment based on the requirements of your project. In the next section, we’ll explore how to handle user authentication in a serverless application using Auth0 and JWT.
In this section, you’re going to add sign-in/sign-out and user-profile features to 24Hour Video. You’ll use Auth0 to handle user sign-up and authentication, and we’ll show you how to secure access to Lambda functions. So far, we’ve focused only on building the 24-Hour Video back end and neglected the front end. You’re now going to build an interface so that users can interact with the system (figure 5.3.)
Furthermore, we’ll introduce the AWS API Gateway in more detail. You can use this AWS service to create an API between back-end services and the front end. The API Gateway is covered in more detail in chapter 7, so feel free to jump to it if you need further information or clarification as you follow this example.
The plan for adding an authentication/authorization system to 24-Hour Video is as follows:
Figure 5.4 shows the authentication/authorization architecture you’re going to build in steps 1–5. Step 6 is described in more detail in section 5.3.5.
At this stage, you might ask, why can’t I get temporary AWS credentials and invoke Lambda directly from the 24-Hour Video website? Why do I need an API Gateway at all? Those are fair questions. You do have two ways of invoking Lambda functions. One way is to use the SDK; the other is to go through an interface created by the API Gateway. If you use the SDK approach, it would mean the following:
When it comes to Lambda and a web application, creating a RESTful interface using the API Gateway and putting your functions behind it is the way to go.
If you’re building a large web application today, you might choose one of the available single-page application (SPA) frameworks such as Angular or React. For the purposes of this example, you’re going to create a website using Bootstrap and jQuery. The reason for doing so is to allow you to focus on the serverless aspects of the system rather than configuration and management of an SPA framework. If you wish to use your favorite SPA rather than vanilla JavaScript and jQuery, feel free to do so. You’ll be able to follow this example with a few minor tweaks. Figure 5.5 shows what this basic website will look like initially.
A quick way to create a skeleton website is to download the Bootstrap version of the Initializr template (you can accept all default settings when downloading) from http://initializr.com. Extract the download to a new directory such as 24-hour-video. You’re going to make changes to this website and install additional packages. To help manage dependencies and later to perform deployments, you’ll use npm as you did for Lambda functions in chapter 3. Open a terminal window and do the following:
npm install local-web-server --save-dev
Run npm start from the terminal and open http://127.0.0.1:8100 in your web browser to see the website.
Now you can integrate Auth0 with the website. Register a new account at https://auth0.com. You’ll need to type in a preferred Auth0 account name, which could be anything (for example, your organization or website name) and select a region (choose US West). After creating the account, you might see an Authentication Providers pop-up. In this pop-up you can choose the types of authentication to offer to your users. They include standard username and password authentication, as well as integration with Facebook, Google, Twitter, and Windows Live. You can configure additional connections or remove the ones you’ve chosen later.
You’ll start with a default app in Auth0 that you can use as a basis for 24-Hour Video. You’ll be given an option to choose an application type (figure 5.6). Select Single Page App and then select jQuery. You’ll be taken to a documentation page that describes how to configure Auth0 for your website. You can always refer to this page for additional information, and you should because Auth0 documentation is excellent. For now, however, click the Settings tab that’s under the Default App heading.
In the Settings tab you’ll need to configure a couple of options (figure 5.7):
Auth0 will send responses to only the URLs that are specified in Allowed Callback URLs. If you forget to specify your website URL there, Auth0 will show an error during sign-in.
You should also look at Connections (on the left menu) to see which types of integrations, such as database-driven, social, enterprise, or password-less, you could use. If you click Social under Connections, you’ll see a list of third-party authentication providers that you can enable for your web application (figure 5.8).
Having two or three social connections enabled is usually enough for most applications. Users will get confused and use multiple accounts to sign in to the system. When that happens, you’ll get questions from people asking why their account is different or why things are missing. It’s possible to link accounts together, but that’s outside the scope of this chapter; see http://bit.ly/1PRKiRe if you need more information on how to do it. Note that the free Auth0 account supports only two social identity providers. If you want to use more, you’ll have to go on a paid plan.
If you decide to enable integration with a third-party identity provider such as Google or GitHub, you’ll need to do a bit of configuration. When you click an identity provider in Auth0, you’ll see the information, such as an API key, that needs to be entered. Auth0 always provides a link to a page that explains how to obtain needed keys, client IDs, and secrets (figure 5.9). For 24-Hour Video, make sure to enable and configure at least one identity provider, such as Google or GitHub, to see how it works. An exercise at the end of the chapter will ask you to do this.
In this section, you’ll connect the website to Auth0. The user will be able to register and sign in to Auth0 and receive their JSON Web Token. This token will be stored in the browser’s local storage and included in every subsequent request to the API Gateway. The user will also be able to sign out, which will remove the token from local storage. Figure 5.10 shows this part of the workflow. Please be aware that in a real system, including this token in every request isn’t a best practice. You should control where the token is sent so that third parties don’t accidentally intercept it. An exercise at the end of the chapter asks you to address this problem.
Auth0 Lock is a free widget from Auth0 that provides a nice-looking sign-in/sign-up dialog box. It simplifies the authentication flow and has a few interesting features (for example, it can remember which identity provider the user used in a previous session). You’re going to use this, so next we’ll look at the following:
To add Auth0 Lock to the website, follow these steps:
You need to add JavaScript to wire up the buttons. Create the following two files in the js directory of the website:
Now add the following lines above <script src="js/main.js"></script> but below <script src="https://cdn.auth0.com/js/lock-9.min.js"></script> in index.html:
<script src="js/user-controller.js"></script> <script src="js/config.js"></script>
Copy the next listing to user-controller.js. This code is responsible for initializing Auth0 Lock, wiring up click events for the buttons, storing the JWT in local storage, and then including it in every subsequent request in the Authorization header.
Copy the code that follows to config.js. Remember to set the correct client ID and Auth0 domain.
Copy the code in the next listing to main.js.
Finally, modify main.css (located in the css directory of the website) to have the styles given in the following listing.
#auth0-logout { display: none; } #user-profile { display: none; } #profilepicture { height: 20px; width: 20px; }
To test Auth0 integration, check that the web server is running in the terminal. If it isn’t, then run it by executing npm start. Open the page in the browser and click the Sign In button. You should see the Auth0 Lock dialog (figure 5.11). Sign up right now (note that you’re creating a new user for the 24-Hour Video app; this isn’t the same user you used to sign up to Auth0 in the first place), and Auth0 should immediately sign you in to your website. The JWT should be transmitted and saved in the browser’s local storage (if you use Chrome, you can open Developer Tools, select Storage, click Local Storage, click http://127.0.0.1:8000, and you’ll see the userToken). Click the Sign Out button to log out and delete the JWT from local storage.
Go back to the Auth0 dashboard and click Users. You’ll see all users registered with the site. You can contact, block, delete, view location, or even sign in as a different user. If you signed in successfully before, you should see your user details in the list.
If something didn’t work and you couldn’t sign in, open your browser’s developer tools and inspect the Console and Network tabs for any messages from Auth0. Double-check that you set the Allowed Callback URL in Auth0 to be the URL of your website, and check that you have the correct client ID and the domain.
Now you’re going to create a Lambda function that will accept the JWT from the website, validate it, and then request more information about the user from Auth0. You could issue a request to Auth0 straight from the browser and get information about the user that way. You don’t need a Lambda function to do this, but this example is designed to show how to deal with JWT in Lambda and, a little later, some of the code will serve as a basis for your custom authorizer.
As we mentioned earlier, there are two ways to invoke a Lambda function: using the AWS SDK or via an API Gateway. We’ll go with the second option, so you need to create an API Gateway. Your website will issue requests to an API Gateway resource and include JWT in the Authorization header of the request. The API Gateway will capture requests, route them to the Lambda function, and then send Lambda responses back to the client. Figure 5.12 shows this part of the workflow.
You’ll now work on the custom API (figure 5.13).
Before implementing a user profile Lambda function, you should create a new IAM role for it. You could reuse the role you created earlier (lambda-s3-execution-role), but it has one too many permissions you don’t need. So let’s see how to make a new one with fewer permissions:
Having created a new role, you can focus on the Lambda function. This function will do the following:
Create the function in AWS right now:
On your computer, set up the function:
You now need to add an npm module called jsonwebtoken. This module will help to verify the integrity of the token and decode it.
In a terminal window change to the directory of the function and run
npm install jsonwebtoken --save
Also, to make a request to Auth0 to retrieve user information, you’re going to use a library called request. Install request by running npm install request --save from the terminal. Your package.json should look similar to the next listing.
Open index.js and replace its contents with code in the next listing. This code is responsible for validating and decoding the token. If it succeeds, it sends a request to the tokeninfo endpoint provided by Auth0. The JWT is included in the body of the request to Auth0. The tokeninfo endpoint returns information about the user, which is then sent back to the website.
Environment variables are Lambda’s way of storing configuration settings, database connections strings, and other useful information without having to embed them in a function. Saving settings in environment variables is highly recommended because it allows developers to update those settings without having to redeploy the function. Environment variables can be changed independently and in isolation from the function. The AWS platform makes environment variables available to the function via process.env (for Node.js). Furthermore, environment variables can be encrypted via KMS, which provides a good way to store important secrets. Chapter 6 has more information on this useful feature.
Deploy the function to AWS by running npm run deploy from the terminal. Finally, you need to create two environment variables for your Lambda function to store the Auth0 domain and the Auth0 secret (figure 5.14). Listing 5.8 uses these two variables to verify the token and issue a request to Auth0. To add these two variables, do the following:
You need to set up an API Gateway to accept requests from your website and invoke the user-profile Lambda function. You also need to create a resource, add support for a GET method, and enable cross-origin resource sharing (CORS):
APIs in the Gateway are built around resources. Every resource can be combined with an HTTP method such as HEAD, GET, POST, PUT, OPTIONS, PATCH, or DELETE. You’re going to create a resource called user-profile and combine it with a GET method. In the API you just created, follow these steps:
Having saved the GET method, you should immediately see the Integration Request screen (figure 5.17):
Next, you need to enable CORS:
If you look at listing 5.8, you’ll see code that refers to event.authToken. This is the JWT token passed in via the Authorization header from the website. To make this token available in a Lambda function, you need to create a mapping in the API Gateway.
In listing 5.9 you’re creating a mapping using the Velocity Template Language (VTL). This mapping extracts a value from the HTTP (method) request and makes it available to your Lambda function (via a property called authToken on the event object). A mapping template transforms data from one format to another. See chapter 7 for more information on mapping templates.
This mapping will extract the Authorization header and add it as an authToken to the event object:
In figure 5.17, you might have noticed a check box labeled Use Lambda Proxy Integration. If you had enabled that check box, the incoming HTTP request—including all headers, query string parameters, and the body—would have been mapped and made available to the function via the event object automatically. This means that you wouldn’t have had to create a mapping template as you did in listing 5.9 (the filename would be accessible from queryStringParameters on the event object). The reason you didn’t do this is because we wanted to show you how to create a custom mapping template and extract only the parameter you need (rather than passing the entire HTTP request to the function). In many cases, proxy integration is very useful and you’ll certainly use it as you progress through the chapters. See chapter 7 for a more in-depth discussion on proxy integration versus manual mapping.
Finally, you need to deploy the API and get a URL to invoke from the website:
The next page you see will show the Invoke URL and a number of options (figure 5.21). Copy the URL, because you’ll need it for the User Profile button.
The final two steps are to update the Show Profile click handler and config.js to invoke the Show Profile Lambda function via the API Gateway. Open user-controller.js in the js folder of the 24-Hour Video website, and add the code shown in the next listing (right after the logout click-handler definition).
Finally, update the contents of config.js to match the next listing. Once you’ve done that, you can test the entire system.
Check that the 24-Hour Video website is running. If it isn’t, run npm start from the terminal (make sure you’re in the website’s directory) and sign in via Auth0. Click the User Profile button. You should see an alert with the contents of the user’s profile in Auth0.
API Gateway supports custom request authorizers. These are Lambda functions that the API Gateway can use to authorize requests. A custom authorizer runs at the method request stage—that is, before the request reaches the target back end. A custom authorizer can validate a bearer token and return a valid IAM policy, which authorizes the request. If the returned policy is invalid, the request is not allowed to continue. To prevent constant invocations of custom authorizers, policies along with the incoming token are cached for an hour.
The benefit of using a custom authorizer is that you can write a dedicated Lambda function to validate the JWT (instead of doing it in every function you want to invoke). Figure 5.22 shows what a modified request flow looks like when a custom authorizer is introduced.
You’re going to implement a custom authorizer now to see how it works. There are three steps:
The first step is to create a regular Lambda function just as you did before:
Deploy the function to AWS once you’ve implemented it. You also need to add the AUTH0_SECRET as an environment variable to the function:
The final steps are to create a custom authorizer in the API Gateway and connect it to the GET method you created previously:
Now you can set your custom authorizer to invoke automatically whenever a GET request to /user-profile is issued:
To test the custom authorizer, make the User Profile button display when the user isn’t logged in. To do that, open main.css and remove the style for #user-profile from it. Also, delete your JWT from local storage and refresh the site. Click the User Profile button. Your custom authorizer should reject the request. You can use this custom authorizer for all Lambda functions down the road.
If you’ve successfully signed in to the 24-Hour Video website and then refreshed after a long period of time, you might see an error message that says, “There was an error getting the profile: 401: Unauthorized.” This could be because the JWT cached in your browser has expired. Sign on to your website again and everything should work again (the message will no longer appear). The default JWT expiration is 36,000 seconds (10 hours), but you can override it in Auth0 or you can choose to implement refresh tokens if you’re up for a challenge (http://bit.ly/2jxbjPg).
Delegation tokens are designed to make integration between services easier. So far, you’ve taken a JSON Web Token supplied by Auth0 and sent it across to AWS where it was verified and decoded by a Lambda function. You had to write a little bit of code to do that. Delegation tokens are created for specific services that know how to decode these tokens and extract claims or information. In effect, delegation tokens are tokens created by one service to call another service or API.
Firebase is a real-time streaming database that we’ll look at in chapter 9. It supports delegation tokens. If a request from a client comes with a delegation token, Firebase knows how to verify it without you having to do anything (or write any code).
To add support for a Firebase delegation token, you need to generate a secret key in Firebase and add it to Auth0. Then your website can request a delegation token from Auth0, which is signed by the secret key from Firebase. Any subsequent request made to Firebase can be sent with the delegation token, which Firebase knows how to decrypt (because it provided the secret key in the first place). In chapter 9, we’ll show you how to provision a delegation token for Firebase in more detail. Similarly, you can set up Auth0 to enable delegated authentication with AWS by setting up a SAML provider and configuring one or more roles.
When it comes to Auth0, to get a delegation token you need to configure an add-on for the service you wish to use and then request the token via the /delegation endpoint. If you wish to integrate with a service such as Firebase or use a delegation token with AWS, you’ll need to enable the appropriate add-on in Auth0 (figure 5.25).
Every add-on has different configuration requirements, so you’ll need to consult relevant Auth0 documentation to find out what’s needed. To set up delegated authentication between Auth0 and AWS, refer to https://auth0.com/docs/aws-api-setup. Another great example is described in https://auth0.com/docs/integrations/aws-api-gateway.
Try to do the following exercises to confirm your understanding of concepts presented in this chapter:
In this chapter, we looked at how to enable authentication and authorization in a serverless application. We looked at how services can communicate directly with the client and checked out JSON Web Tokens. We also introduced Auth0, a service that takes care of many authentication and authorization concerns, and we discussed how delegation tokens can be used across different services. Finally, we stepped through an example where you did the following:
In the next chapter, we’ll look at Lambda functions in much more detail. We’ll consider advanced use cases, see how to use patterns to help implement concise functions without a large number of callbacks, and discuss ways to improve the performance of Lambda-based systems.
18.225.55.151