App Spaces: Building a full-stack app with Google Authentication
Published May 21 2024 09:26 AM 1,546 Views
Microsoft

What is App Spaces? 

App Spaces is a new way to look at building an application in Azure.  App Spaces is different because it’s not a service on its own, but rather an aggregation of existing Azure services with a simplified interface to make it easier to build and manage your web application.  It’s meant to reduce the confusion about which services to use and how to integrate them together end-to-end via a new visual designer. 

 

todo-template-visualizer.png

 

Building an End-to-End Todo App

Does a simple Todo app need 3-tiers (Front-end, backend, and database) with Google authentication and authorization?  Of course not! But its simplicity is why it’s perfect for explaining the concepts which bring it together.  For this application, I used React as a static front-end framework, Express.js running on Node for my backend API, and Postgresql as my database. However, you could mix and match whichever framework meets your needs. 

 

You can follow along with the code from here: https://github.com/azure-template-resources/todo-nodejs-pgsql-googleAuth 

 

Deploying the sample template quickly 

The easiest way to get started is to deploy the entire end-to-end application all at once.  To do this: 

  • Go to App Spaces in the Azure Portal here 
  • Choose the “Todo” sample template 
  • Sign in to GitHub 
  • Choose a target repository – This will fork the Todo repository into your GitHub account so that it can setup continuous deployment. 
  • Hit create 

 

App Space gallery.png

 

This will create a “space” for you which has 3 roles: a Static app running on Static Web Apps (SWA), an API app running on Azure Container Apps (ACA), and a Postgres database installed as an add-on in Azure Container Apps. 

 

One thing you’ll notice is that there is a line drawn between some of the roles.  These aren’t just lines inferring a relationship between roles.  Instead, they represent functional capabilities.  For example, when a Static App is linked to an App Component, the App Component is locked down so that only requests from the Static app will be accepted which becomes the center for our auth story.  And when an App Component is linked to a database, the connection string for that database is automatically injected and managed for you in the App component as environment variables. 

 

Once all 3 roles have completed deploying, click on the “Open app in browser” link on the Static app component.  From there, follow the instruction in the app to learn how to setup Google authentication and authorization. 

 

Breaking the application down 

Okay, while it’s great to be able to run the application with a few clicks, let’s talk about the important pieces of how it’s all put together. 

 

The Static app front-end 

The rendering logic is all stored in App.tsx, but that’s just standard React stuff which isn’t too interesting.  The more important stuff is actually in client.ts which is responsible for calling the backend.  The thing to notice is that all API calls are prefixed under the “/api” path.   

 

client.ts

 

 

 

let baseHostUrl = '/api';

export const getItems = async () => {
    return await fetch(`${baseHostUrl}/items`);
} 

 

 

 

 

This seems weird right?  This is a static front-end app so it shouldn't support hosting APIs.  So why is it calling APIs under its own domain?  Well remember the linking between the Static app and the API app component?  This is how SWA is proxying calls for you to that secure API endpoint.  The SWA runtime is essentially intercepting ajax requests under the ‘/api’ path to your linked backend.  This is important because SWA will secure access to your API backend so that only calls from your front-end via this path will be allowed.  So if you want to leverage their auth scheme, you’ll need to route calls to your backend under this path.  Do you have to use their auth scheme?  Of course not!  You’re more than welcome to handle auth yourself, but trust me that SWA makes this a lot simpler. 

 

Setting up Authentication

The type of provider you would like to setup authentication with is configured in the staticwebapp.config.json file.  For this app, you'll see we're using Google authentication.

 

staticwebapp.config.json - Authentication settings

 

 

 

"auth": {
  "rolesSource": "/api/getroles",
  "identityProviders": {
    "google": {
      "registration": {
        "clientIdSettingName": "GOOGLE_CLIENT_ID",
        "clientSecretSettingName": "GOOGLE_CLIENT_SECRET"
      }
    }
  }
}

 

 

 

 

The other thing worth noting is the rolesSource property.  This property specifies the API in your backend that you would like SWA to call after it has authenticated the incoming user request.  The API will be passed information about the user with any claims that are set on the identity so that you can decide what "roles" you would like to assign that user for authorization (we'll get to talking about roles in just a bit).  Of course, if you don't want to think about the schema for this don't worry, App Spaces will help set all of this up for you!

 

App Space authentication settings

auth-settings.png

 

NOTE: If you want to learn about how to get the Client ID and Client Secret for Google, I recommend installing the template for this application which will walk you through the entire process of getting authentication and authorization setup.

 

Setting up Authorization

As I mentioned before, once your user is authenticated, SWA will call the API defined by the rolesSource property to get the roles the user is associated with.  The response from this API is just a simple array of strings which correspond to roles which are defined in the staticwebapps.config.json file for each route and method combination (we'll talk about this in the back-end section below).  This is where the simplistic auth-scheme for SWA really shines.

 

staticwebapps.config.json - Route definitions

 

 

 

"routes": [
{
  "route": "/api/*",
  "methods": [
    "GET"
  ],
  "allowedRoles": [
    "authenticated"
  ]
},
{
  "route": "/api/*",
  "methods": [
    "PUT",
    "POST",
    "PATCH",
    "DELETE"
  ],
  "allowedRoles": [
    "contributor"
  ]
}

 

 

 

 

In the example above, you can see that a user is authenticated, they can perform GET requests to the API, which is basically equivalent to READ permissions.  But if they get the "contributor" role, then they can also perform PUT, POST, PATCH, and DELETE operations, which is equivalent to WRITE permissions.  This example shows API calls, but you can also write routes and setup roles for even navigational paths within your single page application.

 

And again, if you don't want to worry about the schema for this, App Spaces will make configuring this easy for you.

 

App Space - Route settings

 

Starshot-routes.png

The API App component backend 

 
Connecting to the database

Most of the backend code is just plain Express code, however there are a couple of points worth noting.  First, in queries.js, you’ll notice that there is a reference to the POSTGRES_URL environment variable.  Where does that get set?  Well, via the link between the App component and the database of course!  Once those components are linked together, you don’t have to think about it anymore. 

 

queries.js

 

 

 

const connectionString = process.env.POSTGRES_URL; 

 

 

 

 

To get the full list of environment variables for this link, just click on the connection between your API app component and your database.  You can also use this to map the environment variables to a custom environment variable names as well.

 

App Space connection details between API app and database

starshot-binding-env-vars.png

 

Returning the list of roles for authorization

As mentioned in the client section, SWA will call the API defined by the rolesSource property after the user is authenticated.  In this case, the API route "/api/getroles" is defined in app.js (sorry I got lazy and just coded this up in JavaScript instead of TypeScript, but the code should be about the same).  My code is obviously oversimplifying authorization logic, but it basically just checks to see if your alias matches whatever is in line 8.  If it does, then it will give you the "contributor" role.  If not, then you'll get no custom roles, however SWA will still automatically give you the "authorized" role, which is a built-in role to the service.

 

app.js - getRoles API definition

 

 

 

const getRoles = (req, res) => {
  const body = req.body;
  if (body.identityProvider !== 'google') {
    res.status(401).end();
    return;
  }

  if (body.userDetails === 'yourGoogleAlias@gmail.com') {
    res.send({
      "roles": [
        "contributor"
      ]
    });

    return;
  }

  res.send({
    "roles": [
    ]
  });
}

app.post('/api/getroles', getRoles);

 

 

 

 

Once SWA receives the list of roles, it will use that to figure out which routes the authenticated user has access to.

 

Next steps 

And that's it!  That’s basically all you need to know to learn how to stitch a complete end-to-end application together.  Now that you have a basic understanding of how to leverage App Spaces to make this process easier, here’s some other things to investigate to continue your learning journey: 

 

Co-Authors
Version history
Last update:
‎May 21 2024 10:12 AM
Updated by: