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! πŸ”₯

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs