- Sharing REST API specification
- Serverless Swagger UI solution architecture
- Creating mock API Gateway
- Hosting Swagger UI website on S3
- Building Swagger UI website
- Deploying website to S3 with Serverless
- Authenticating with Cognito
- Exposing website by CloudFront
- Creating Cognito User and Identity Pools
- Retrieving resources parameters
- Performing user authentication
- Testing Cognito user authentication
- Fetching OpenAPI Spec from API Gateway
- Conclusion
Amazon API Gateway provides an option to export the API schema as an OpenAPI Specification. With it, we can display our REST API as an interactive website. But we do not get a public URL to that specification file which we could use as a source for an interactive page like Swagger. Instead, we can only get the file from the AWS Console, CLI, or SDK.
This is why we need to do a few additional steps to get our beautiful documentation working. As a result, we will get a fully customizable website, with easy to implement access protection. And, maybe the most important, it will be always up-to-date, with no work required after changes in the API Gateway endpoints.
This is quite a long post with a step-by-step implementation guide. If in a hurry, jump straight to the end, where you will find a link to the repository with full implementation.
Sharing REST API specification
Amazon API Gateway is a basic building block for most serverless AWS applications. It allows creating a serverless API for Lambda functions, existing HTTP services, and any other AWS service.
When we build an API we often need to share it – with other developers, other teams, our clients, or publicly. And the de facto standard for sharing REST API docs is to create OpenAPI Specification (OAS) and visualize it as an interactive website, for which the most popular engine is the Swagger UI.
API Gateway allows generating such specification but provides no built-in solution to share it. You don’t get a public URL to access the OAS. You can only download it.
I cannot imagine having to manually export the OAS file after every API deployment and send it by email or save on some Confluence page to provide it for other people. Or even uploading it by-hand to a self-hosted Swagger UI page. It’s the kind of a process that everyone will forget to do on the nearest occasion.
But fear not, here is how to create a serverless, interactive, and always up-to-date Swagger UI for the API Gateway. Moreover, with a few additional lines of code, we can make it protected with user authentication using Amazon Cognito.
Serverless Swagger UI solution architecture
I will use the Serverless Framework to build and deploy all resources to AWS. Here is the architecture of the whole solution.
The Swagger UI website will be built and deployed to the S3 bucket. From there it will be hosted as a static website.
To limit access only to authenticated users we will use the Amazon Cognito service. Cognito requires our page to use the HTTPS. S3 website hosting does not support that, so we will use a CloudFront as a simple proxy that will provide an HTTPS URL for the website.
Here is the flow of what will happen when the user opens the page:
- Load website files from the S3 bucket, via CloudFront
- Check if the user is already authenticated
- If not, redirect him to the Cognito auth page
- After successful authentication, Cognito will redirect the user back to our page
- Obtain OpenAPI specification from the API Gateway
- Display Swagger UI
Creating mock API Gateway
We will start with a simple REST API with two mocked endpoints, one GET and one POST. Mock endpoints are easy to define with Serverless.
After deploying this stack, we should have a working API Gateway that we can test.
Hosting Swagger UI website on S3
Building Swagger UI website
The next step is to build and deploy the Swagger UI website. As it’s a simple single-page application, the S3 bucket with the website hosting feature will be the easiest and cheapest solution.
A custom Swagger UI website can be built with the swagger-ui-dist package. We do not want to copy all the frontend files to our project by hand and committing them to our repository, we treat it just like any other library.
The website will be built with webpack.
webpack.config.js
configuration file:
src/index.ejs
template:
Our stylesheet is simple as well. Its role is to load the Swagger UI styles and improve the overall page style a little bit.
And finally, the scripts that are the heart of our website.
Firstly, we need a script to handle Swagger UI displaying. For now, we will load the sample “Petstore” API spec, just to check if the UI works correctly.
Secondly, the main script. It will load the styles and init Swagger UI.
Deploying website to S3 with Serverless
Building and deploying a website should happen every time we deploy the Serverless stack. We do not want to remember it and do it manually every time. Automating it takes it out of our responsibilities.
Two Serverless framework plugins come here with help. The serverless-scriptable-plugin will allow website build commands execution as part of the deployment process, and the serverless-s3-deploy will upload the build output to the S3 bucket.
We also need to create the S3 bucket itself.
We do not enable website hosting in the S3 bucket.
It’s not needed, since Swagger UI does not require to load index.html
for any other path than the root path (/
). We can keep files in this bucket private, only allowing CloudFront to access them.
Authenticating with Cognito
The next part is to restrict access to the API documentation. For this, we will use AWS Cognito, which gives an out-of-the-box solution for user management and authentication.
Exposing website by CloudFront
The simplest solution for website hosting is to use a built-in option in S3 bucket. The drawback of it is that the page is available only over HTTP, not HTTPS. In the authentication process we will be redirecting the user between our website and Cognito’s one. The Cognito does not allow redirects back to not-secured URLs, which is wise, since the redirect contains an access token that is better kept not exposed.
Because of this, we must expose our S3-hosted website by the CloudFront. The CloudFront can provide us an SSL certificate and host the content by the HTTPS.
The biggest drawback of the CloudFront is the creation and update time. Despite the fact we are using it here as a simple proxy, it’s, in fact, a global CDN service. Its deployment, with all the changes propagation over the world, takes a while.
In our CloudFront Distribution, for all requests we return files from the S3 bucket. Because objects in the bucket are not public, we need to setup access to them for the CloudFront. We also make sure that all clients are always using HTTPS.
Creating Cognito User and Identity Pools
The Cognito User Pool will manage users with access to the Swagger website and provide an authentication mechanism. Thanks to that we don’t need to create a login form by ourselves – we will use the one provided by the Cognito. This reduces our work to the minimum.
We need to create 3 resources for this – the User Pool itself, a domain name to be used for our login page, and an App Client for our website, so it will be able to use the authentication mechanism. Change the domain name to something unique, otherwise the deployment will most likely fail.
The next piece is the Cognito Identity Pool. It’s a service that will provide our authenticated users temporary AWS credentials.
We create and assign to the users the SwaggerUIAuthRole
. It has an access policy allowing them to obtain the API Specification from our API Gateway.
This will be needed in our final step, where we will replace the sample Petstore API definition with the actual specification fetched from our API Gateway.
The serverless-cloudformation-sub-variables plugin will ease referencing resources.
Retrieving resources parameters
To interact with the resources we created we need to put their generated parameters (like User Pool ID) in the website configuration. We could, of course, deploy everything, check the generated values, and then hardcode them into the page code. However, that would make the solution working only for our single environment, which is, obviously, a bad idea.
To solve that, we will add a script extracting all the required parameters from the stack. It will be run after the stack deployment, but before building the website. The parameters will be saved as a JSON file that later can be read from the page scripts.
The script, run from Serverless, uses its SDK to execute AWS SDK commands.
Read more about that approach in the article about auto-generating environment parameters.
Performing user authentication
The Cognito offers a built-in login panel, but we still need to integrate our website with it. It will require a few changes in the website code.
Firstly, we will add a topbar to the page to display the currently logged in user and allow him to log out.
Secondly, we need to read the generated config parameters file.
Now, having all the required parameters, we can integrate with the Cognito. To do so we will use the AWS Amplify library. The logic is pretty trivial. Apart from configuring the Amplify, we only store the current authentication process state.
The last piece is to actually start the authentication process. We do that right away when the user opens the page.
Testing Cognito user authentication
With all of the above in place, we can test the user authentication on our website.
Deploy the updated stack. Then go to the Cognito User Pool in the AWS Console and create a new user. Next, go to the CloudFront and find the domain name for our distribution. Open that page.
As soon as you open the website, it should redirect you to the Cognito hosted UI website with the login form. Type the created user’s credentials and you should be redirected back to our Swagger website, with the user’s email displayed in the top bar.
Fetching OpenAPI Spec from API Gateway
With secured access to the Swagger website, we can finally obtain the actual specification of our API. To have this specification always up-to-date we fetch it directly from the API Gateway. To do this, we use the API Gateway SDK with access credentials we get for our authenticated user. Those credentials allow us to make a call to export API Specification, accordingly to previously configured IAM policy.
Now, after you deploy the stack and open the page, you should see the documentation for our API. You can call the mock endpoints from it.
If, after the deployment, you still see the previous version of the page with the PetStore API, go to the CloudFront Distribution and invalidate its cache.
Conclusion
With a minimal amount of code and few AWS services, we can build a secured, serverless website with Swagger UI documenting our API. This makes it a great way to share the API specification with other developers.
All of the used AWS services are available in the Free Tier offer. But even after the Free Trial, with only the (not public) usage by other development teams integrating with the API, the costs should be minimal. For the details see the S3, CloudFront, and Cognito pricing pages.
The documentation presented in Swagger UI may be enriched by specifying additional models and descriptions for the API elements. Check out the serverless-aws-documentation plugin that reduces the boilerplate when doing that with Serverless.
The full solution is available as the GitHub repository:
It contains additional elements, like launching the Swagger UI locally for the development and clearing the website bucket when removing the stack. Make sure to check it out!
Update 2020-11-02
Since Swagger UI, despite being a Single Page Application, does not use any other paths than the root path (/
) that should load index.html
, we do not have to use S3 website hosting.
We can keep objects in bucket private, giving access only for the CloudFront.
This should be preferred way, limiting access wherever possible.
I’ve updated code examples with a change for it.