DEV Community

Champ of Greatness
Champ of Greatness

Posted on

๐Ÿ” Secure Azure Static Web Apps (SWA) with C# Azure Functions & Authentication

๐Ÿ“Œ Overview

In this guide, you'll learn how to:

โœ… Set up Azure Static Web Apps (SWA) locally using the SWA Emulator.

โœ… Run SWA with Vite for a fast development experience.

โœ… Authenticate users in an Azure Static Web App using GitHub login.

โœ… Capture authentication data in a C# Azure Function.

โœ… Secure API endpoints to allow only authenticated users.

โœ… Forward authentication details to external APIs (if needed).

By the end, you'll have a fully working React + Vite + Azure SWA + C# API setup with authentication! ๐Ÿš€


๐Ÿ“Œ Step 1: Set Up an Azure Static Web App Locally with the Emulator

Azure Static Web Apps automatically handle authentication when deployed to Azure. However, when running locally, we need to simulate the authentication behavior using the Azure SWA Emulator.

โœ… Install the Azure Static Web Apps CLI

First, install the emulator CLI globally:

npm install -g @azure/static-web-apps-cli
Enter fullscreen mode Exit fullscreen mode

โœ… Create a New React + Vite Project

If you donโ€™t have a Vite project yet, create one:

npm create vite@latest my-swa-app --template react
cd my-swa-app
npm install
Enter fullscreen mode Exit fullscreen mode

โœ… Start the Vite Development Server

Run your React app locally:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Vite will start on port 5173, and youโ€™ll see output like:

  VITE v4.0.0  ready in 300ms
  โžœ  Local: http://localhost:5173/
Enter fullscreen mode Exit fullscreen mode

โœ… Start SWA Emulator (Proxy for Authentication)

Now, in a new terminal window, start the Azure SWA emulator pointing to Viteโ€™s port:

swa start http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

This will:
โœ” Start an auth proxy (default: http://localhost:4280).

โœ” Simulate Azureโ€™s authentication locally.

โœ” Enable login/logout via GitHub, Microsoft, etc.

โœ… Access Your App via the SWA Proxy

Go to:

http://localhost:4280
Enter fullscreen mode Exit fullscreen mode

This ensures authentication works just like in Azure! โœ…


๐Ÿ“Œ Step 2: Add Authentication in React

Now, letโ€™s add a login/logout system to our React (Vite) app.

โœ… Create an Authentication Context (AuthContext.js)

import React, { createContext, useState, useEffect, useContext } from "react";

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/.auth/me")
      .then((res) => res.json())
      .then((data) => setUser(data.clientPrincipal || null))
      .catch(() => setUser(null))
      .finally(() => setLoading(false));
  }, []);

  const login = (provider = "github") => {
    const isLocal = window.location.hostname === "localhost";
    const authBaseUrl = isLocal ? "http://localhost:4280" : "";
    window.location.href = `${authBaseUrl}/.auth/login/${provider}?post_login_redirect_uri=/dashboard`;
  };

  const logout = () => {
    localStorage.removeItem("user");
    setUser(null);
    window.location.href = "/.auth/logout";
  };

  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);
Enter fullscreen mode Exit fullscreen mode

โœ… Add Login & Logout Buttons

import { useAuth } from "./AuthContext";

const LoginPage = () => {
  const { login } = useAuth();
  return <button onClick={() => login()}>Login with GitHub</button>;
};

const LogoutButton = () => {
  const { logout } = useAuth();
  return <button onClick={logout}>Logout</button>;
};
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Œ Step 3: Capture Authentication in a C# Azure Function

Once a user logs in, authentication details are sent via x-ms-client-principal, which needs to be decoded in your Azure Function.

โœ… C# Azure Function to Capture User Authentication

using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

public static class GetAuthenticatedUser
{
    [FunctionName("GetAuthenticatedUser")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Checking authentication for user.");

        string principalHeader = req.Headers["x-ms-client-principal"];
        if (string.IsNullOrEmpty(principalHeader))
        {
            return new UnauthorizedObjectResult("Unauthorized: No authentication token found");
        }

        try
        {
            byte[] decodedBytes = Convert.FromBase64String(principalHeader);
            string decodedString = Encoding.UTF8.GetString(decodedBytes);
            var user = JsonSerializer.Deserialize<ClientPrincipal>(decodedString);

            return new OkObjectResult(new { user.IdentityProvider, user.UserId, user.UserDetails, user.UserRoles });
        }
        catch (Exception ex)
        {
            log.LogError($"Error decoding authentication token: {ex.Message}");
            return new StatusCodeResult(StatusCodes.Status500InternalServerError);
        }
    }
}

public class ClientPrincipal
{
    public string IdentityProvider { get; set; }
    public string UserId { get; set; }
    public string UserDetails { get; set; }
    public string[] UserRoles { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Œ Step 4: Restrict API Access

By default, your API is public. To require authentication, create a _routes.json file in public/.

{
  "routes": [
    {
      "route": "/api/GetAuthenticatedUser",
      "allowedRoles": ["authenticated"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

โœ” Now, only logged-in users can access /api/GetAuthenticatedUser.


๐Ÿ“Œ Step 5: Forward Authentication to an External API

If you need to pass authentication details to another API, use:

using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

public static class ForwardAuthFunction
{
    private static readonly HttpClient client = new HttpClient();

    [FunctionName("ForwardAuth")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Forwarding authentication token to external API.");

        string principalHeader = req.Headers["x-ms-client-principal"];
        if (string.IsNullOrEmpty(principalHeader))
        {
            return new UnauthorizedObjectResult("Unauthorized");
        }

        var externalApiUrl = "https://external-api.com/protected";
        var requestMessage = new HttpRequestMessage(HttpMethod.Get, externalApiUrl);
        requestMessage.Headers.Add("Authorization", $"Bearer {principalHeader}");

        var response = await client.SendAsync(requestMessage);
        var responseBody = await response.Content.ReadAsStringAsync();

        return new OkObjectResult(responseBody);
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“Œ Conclusion

๐ŸŽ‰ Now you have a React + Vite + Azure SWA + C# API with authentication! ๐Ÿš€

โœ… Run locally with the SWA Emulator.

โœ… Authenticate users with GitHub.

โœ… Capture user info in a C# Function.

โœ… Secure API routes with _routes.json.

โœ… Forward authentication details to other APIs.

Need more? Let me know! ๐Ÿ”ฅ

Top comments (0)