๐ 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
โ 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
โ Start the Vite Development Server
Run your React app locally:
npm run dev
Vite will start on port 5173, and youโll see output like:
VITE v4.0.0 ready in 300ms
โ Local: http://localhost:5173/
โ 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
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
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);
โ 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>;
};
๐ 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; }
}
๐ 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"]
}
]
}
โ 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);
}
}
๐ 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)