#OpenToWork

Available for new opportunities! Let's build something amazing together.

Get in touch

Adding authentication to your Astro site with Azure Static Web Apps

post

Azure Static Web Apps provides built-in authentication and authorization capabilities, making it easy to secure your Astro site and API endpoints using Microsoft Entra ID (previously Azure AD) authentication. In this guide, I will walk you through the process of setting up authentication, handling user roles, and testing everything locally.

important

To follow this post, it is important first to read the Astro site deployment to Azure Static Web Apps with the CLI from GitHub Actions and Integrating Azure Functions into your Astro site articles.

Why use Azure Static Web Apps authentication?

With Azure Static Web Apps, authentication is managed for you, eliminating the need to handle OAuth flows, token storage, or authentication logic manually. Out of the box, Azure Static Web Apps provides:

  • Easy role-based access control (RBAC)
  • Built-in integration with Microsoft Entra ID (formerly Azure AD)
  • Simple login/logout endpoints
  • Automatic authentication handling for API routes
  • Best of all, it is included in the free plan

Now, let’s dive into the implementation.

What we want to secure

For this guide, we will secure the following routes:

  • /success: A protected route that only users with the admin role can access
  • /api/*: A protected API route that only users with the admin role can access

We also add the following routes to handle authentication:

  • /login: A login route that redirects users to the Microsoft Entra ID login page
  • /logout: A logout route that logs users out of the application

The root route (/) will be left unprotected, allowing all users to access it and click the login button.

Step 1: Configure your routes and authentication settings

The first step is to configure your routes and authentication settings in the staticwebapp.config.json file.

Start by creating a staticwebapp.config.json file in your project’s root directory with the following contents:

staticwebapp.config.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
  "trailingSlash": "auto",
  "navigationFallback": {
    "rewrite": "/index.html"
  },
  "routes": [
    {
      "route": "/success",
      "allowedRoles": ["admin"]
    },
    {
      "route": "/api/*",
      "allowedRoles": ["admin"]
    },
    {
      "route": "/login",
      "rewrite": "/.auth/login/aad?post_login_redirect_uri=/success"
    },
    {
      "route": "/logout",
      "redirect": "/.auth/logout"
    }
  ],
  "responseOverrides": {
    "401": {
      "redirect": "/login",
      "statusCode": 302
    }
  }
}

This configuration does the following:

  • Redirects unauthorized users to /login when they try to access protected routes (/success, /api/*)
  • Protects /success and /api/* routes so only users with the admin role can access them
  • Defines a login route (/.auth/login/aad), redirecting users to /success after authentication
  • Provides a logout route (/.auth/logout)

Step 2: Copying the configuration file to the build output

The staticwebapp.config.json needs to be copied to the output directory of the Astro site as the Static Web App CLI will not automatically do this for you. To achieve this, you can use an Astro config hook to copy the file to the output directory.

Creating the Astro config hook

Create a new file: ./app/utils/swaConfigIntegration.ts with the following content:

./app/utils/swaConfigIntegration.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import type { AstroIntegration } from "astro";
import { promises as fs } from "fs";
import { join } from "path";

async function swaConfigIntegration(): Promise<void> {
  try {
    const __dirname = new URL(".", import.meta.url).pathname;
    const configPath = join(__dirname, "../../staticwebapp.config.json");
    const destinationPath = join(__dirname, "../dist/staticwebapp.config.json");
    await fs.copyFile(configPath, destinationPath);
  } catch (error) {
    console.error("Error copying SWA config file", (error as Error).message);
  }
}

export default {
  name: "swaConfigIntegration",
  hooks: {
    "astro:config:setup": async () => {
      await swaConfigIntegration();
      console.log("✅ SWA config integration complete");
    },
  },
} satisfies AstroIntegration;

Updating astro.config.mjs

With the SWA Config integration hook in place, you can update the astro.config.mjs file to include it to the build process:

./app/astro.config.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// @ts-check
import { defineConfig } from "astro/config";

import react from "@astrojs/react";

import tailwindcss from "@tailwindcss/vite";
import swaConfigIntegration from "./utils/swaConfigIntegration";

// https://astro.build/config
export default defineConfig({
  integrations: [react(), swaConfigIntegration],

  vite: {
    plugins: [tailwindcss()],
  },
});

Step 3: Creating and updating the pages

Now that we have the configuration in place, we can continue by creating and updating the pages.

Updating the root page

On the root, we want to add a login button, that way, users can log in to the application. Update the ./app/src/pages/index.astro file with the following content:

./app/src/pages/index.astro
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
---
import Layout from "../layouts/Layout.astro";
---

<Layout>
  <div class="flex items-center justify-center h-screen">
    <a
      href="/login"
      class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
    >
      Login with Entra ID
    </a>
  </div>
</Layout>

Creating the success page

In the routes above, when the user signs in, they will get redirected to a /success page. Create a success page ./app/src/pages/success.astro that users will see after logging in:

./app/src/pages/success.astro
1
2
3
4
5
6
7
8
---
import Welcome from "../components/Welcome.astro";
import Layout from "../layouts/Layout.astro";
---

<Layout>
  <Welcome />
</Layout>

Step 4: Testing the authentication locally

To test the authentication locally, you can use the Static Web Apps CLI. Run the following command in your terminal:

Run your Astro site and Azure Functions
1
npm run dev
important

The npm run dev command has been configured in the previous articles. Check out: Astro site deployment to Azure Static Web Apps with the CLI from GitHub Actions and Integrating Azure Functions into your Astro site

Visit http://localhost:4280, click the login button, and manually enter user claims in the authentication simulator.

Show image Local authentication testing
Local authentication testing
alert

By default, the user gets the anonymous and authenticated roles. For this example, manually add the admin role, otherwise you would not be able to reach the /success page.

Understanding the Authentication Flow

  1. Users visit your site and click the Login with Entra ID button
  2. They are redirected to /.auth/login/aad for authentication
  3. Upon successful login, they are redirected to /success
  4. API endpoints (/api/*) remain protected and require authentication

Step 5: Configure the user roles on Azure Static Web Apps

When you completed the local testing, you can configure the user roles on Azure Static Web Apps. Follow these steps:

  1. Open the Azure portal
  2. Navigate to your Azure Static Web App
  3. Click Role Management
  4. Select Invite and enter the user’s email
  5. Assign the admin role
  6. Click Generate and share the invite link with the user
Show image Role assignment on the Azure Static Web App
Role assignment on the Azure Static Web App

Step 6: Time for the deployment

To deploy, you can use the same GitHub Actions workflow from the previous articles:

Conclusion

Adding authentication to your Astro site with Azure Static Web Apps is straightforward (if you do not forget to add the config to your output folder). The built-in authentication system handles the complex security aspects while you can focus on building your application.

Comments

Back to top