For our full stack web application project, my capstone team has chosen an application architecture that consists of hosting the front-end application (user interface) and back-end application (REST API and database) separately on different servers, resulting in two URLs with different domain names. This configuration has the advantages of enforcing the design principal of separation of concerns and allowing for greater development and deployment flexibility. We are using AWS Amplify to deploy and host the front-end application built with React. For the back-end, built using Java and Spring Boot, we are using AWS Elastic Beanstalk to orchestrate deployment and hosting using various services such as EC2 and Elastic Load Balancing. While Elastic Beanstalk could also be used to provision our PostgreSQL relational database, we have chosen to separate this component instead, and we have manually provisioned the database using Amazon RDS. This allows us even greater flexibility, with the ability to deploy back-end changes without any unneeded impact to the database layer.
Although we are using two AWS services for deploying and hosting our front-end and back-end, each application ends up running on different compute instances with different domain names for accessing each on the internet. While there are benefits to this separation, there have been some local development and production environment issues that needed to be addressed. The first is Cross-Origin Resource Sharing (CORS). CORS is a “mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.” This specification also applies to cross-origin requests made to our back-end REST API, but such requests are not enabled by default in our application framework, resulting in a blocked request message indicating that an
Access-Control-Allow-Origin header is missing on the requested resource. We actually discovered this restriction while running the front-end app locally during development, which was making requests to the back-end running on AWS. Allowing cross-origin requests involves some back-end application configuration, and addressing this issue for local development also resolves it when the application is running in production on AWS, as both environments use cross-origin requests.
Another issue we ran into was mixed content. AWS Amplify hosts our front-end application with a domain using HTTPS, which uses a secure protocol (TLS) to encrypt the web server connection and any requests and responses to/from the server. Elastic Beanstalk, on the other hand, uses HTTP by default. While attempting to make requests from the front-end pages served over HTTPS to the back-end using unencrypted HTTP, such requests are blocked by the browser. This is a security feature to protect against man-in-the-middle attacks and protect other vulnerabilities. While there may be various workarounds for getting past mixed-content blocking, we determined the best strategy to resolve this issue would be to also serve the back-end content over HTTPS instead of HTTP. We would come to find out this is not as straight forward as resolving the CORS issue.
Use a custom domain
There are three different ways to use HTTPS with Elastic Beanstalk. Two of these methods involve setting up our own SSL certificate. Fortunately, I just so happened to already have a custom domain configured with a certificate on AWS. This allowed us to use the third option, and the easiest one, which is route traffic from the custom domain to the Application Load Balancer (ALB) configured for our back-end application by Elastic Beanstalk. Amazon Route 53 is a Domain Name Service (DNS) that allows us to do just that.
As mentioned, we already have a certificate, and it is configured with AWS Certificate Manager. We just need to ensure our certificate is set up with our domain name, both with and without “www” in front.
Add a secure connection to our backend application
On the EC2 Dashboard in the AWS management console, in the Load Balancers section, we already have an HTTP listener set up for our Application Load Balancer. We can also add an HTTPS listener, which uses encrypted connections between clients and the load balancer:
A security group is associated with the load balancer to control traffic that is allowed to reach, as well as leave the ALB. Since we’ve configured the load balancer to listen for HTTPS requests, we need to also add an Inbound Rule to the security group to allow incoming HTTPS traffic:
Deploying and hosting our full stack application provided many learning opportunities for our team. We are able to use various AWS services that are new to us, and we encountered issues that required our combined resourcefulness to resolve. These are high-level steps we’ve taken to configure our application to allow Cross-Origin Resource Sharing and to enable HTTPS for our back-end REST API to satisfy the security concern of mixed-content resource blocking.