Skip to content

ghidello/AngularAspireNegotiate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Angular Aspire Negotiate

This repository is a sample project that demonstrates how to build and run a .NET 10 Web API protected by Windows Authentication using the Microsoft.AspNetCore.Authentication.Negotiate package, with an Angular SPA served by the API in production.

The solution is intended to show the full development and hosting flow for a same-origin Angular + ASP.NET Core application where the API owns authentication and the SPA is deployed into the API project's wwwroot folder. Because the SPA and API are hosted from the same origin in production, no CORS configuration is needed.

Goals

  • Configure a .NET 10 Web API to use Windows Authentication through Negotiate.
  • Host the Angular production build from the API project's wwwroot folder.
  • Configure an Angular development proxy that supports authentication negotiation when calling the API during local development.
  • Orchestrate the API and Angular client during development with .NET Aspire.
  • Use HTTPS only for the local development experience.
  • Configure Aspire so the Angular client is exposed through HTTPS during development.
  • Configure Content Security Policy headers for both the Angular development server and the .NET API host.

Project Layout

  • aspire/AppHost: .NET Aspire orchestration project for the local development environment.
  • aspire/ServiceDefaults: Shared Aspire service defaults.
  • aspire.config.json: Aspire CLI configuration that points to the AppHost project.
  • .github/skills: Agent skills generated by the Aspire CLI for Aspire, .NET API inspection, and Playwright workflows.
  • .vscode/mcp.json: Workspace MCP configuration generated by the Aspire CLI so VS Code agents can use the Aspire MCP server.
  • .vscode/settings.json: Workspace settings that associate aspire.config.json with the Aspire CLI JSON schema.
  • src/Web: ASP.NET Core Web API host secured with Windows Authentication.
  • src/Web.Angular: Angular SPA that is served by the API in production, run with the Angular dev server during development, and included in the solution through Web.Angular.esproj.

Tooling Configuration

The root aspire.config.json file tells the Aspire CLI where the AppHost project lives. It can be created with aspire config set commands:

{
	"appHost": {
		"path": "./aspire/AppHost/AppHost.csproj",
		"language": "csharp"
	}
}

The workspace .vscode/settings.json file associates that config file with the Aspire CLI schema so VS Code can provide validation and completion:

{
	"json.schemas": [
		{
			"fileMatch": [
				"/aspire.config.json"
			],
			"url": "https://aspire.dev/reference/cli/configuration/schema.json"
		}
	]
}

The Aspire CLI agent integration was initialized with:

aspire agent init

That command added workspace agent support files, including .github/skills and .vscode/mcp.json. The MCP configuration starts the Aspire MCP server through the Aspire CLI:

{
	"servers": {
		"aspire": {
			"type": "stdio",
			"command": "aspire",
			"args": [
				"agent",
				"mcp"
			]
		}
	}
}

Aspire HTTPS Orchestration

The Aspire AppHost runs both projects under src during local development:

  • src/Web is added as a .NET project resource named web.
  • src/Web.Angular is added as a JavaScript app resource named angular and runs the Angular start script.
  • The Angular app waits for the Web API and references it so Aspire can provide service discovery details.
  • The Angular app is exposed with an HTTPS endpoint only.
  • Aspire provides a development certificate to the Angular dev server and passes the generated certificate and key paths to Angular CLI with --ssl, --ssl-cert, and --ssl-key.
  • The Web API launch profile was reduced to HTTPS only by removing the HTTP profile and leaving https://localhost:7219 as the application URL.

The AppHost configuration currently looks like this:

var builder = DistributedApplication.CreateBuilder(args);

var web = builder.AddProject<Projects.Web>("web");

#pragma warning disable ASPIRECERTIFICATES001
builder.AddJavaScriptApp("angular", "../../src/Web.Angular", "start")
	.WaitFor(web)
	.WithReference(web)
	.WithHttpsEndpoint(env: "HTTPS_PORT")
	.WithHttpsDeveloperCertificate()
	.WithHttpsCertificateConfiguration(static ctx =>
	{
		ctx.Arguments.Add("--");
		ctx.Arguments.Add("--port");
		ctx.Arguments.Add("%HTTPS_PORT%");
		ctx.Arguments.Add("--ssl");
		ctx.Arguments.Add("--ssl-cert");
		ctx.Arguments.Add(ctx.CertificatePath);
		ctx.Arguments.Add("--ssl-key");
		ctx.Arguments.Add(ctx.KeyPath);

		return Task.CompletedTask;
	});
#pragma warning restore ASPIRECERTIFICATES001

builder.Build().Run();

The ASPIRECERTIFICATES001 warning is suppressed around the Angular dev server certificate configuration because this sample intentionally uses Aspire's development certificate support to run the SPA over HTTPS during local development.

Protected API Surface

The Web API groups protected sample endpoints under /api and requires authorization for the whole group:

var api = app.MapGroup("/api")
	.RequireAuthorization();

The current sample endpoints are:

  • GET /api/user: returns the current authenticated Windows identity with name, authenticationType, and isAuthenticated.
  • GET /api/weatherforecast: returns the sample weather forecast from the default ASP.NET Core template, moved under the protected API group.

Authentication and authorization middleware are enabled before the route group is executed:

app.UseAuthentication();
app.UseAuthorization();

The sample intentionally does not return user claims from /api/user; the page only displays the current identity summary.

Angular Client

The generated Angular starter page was replaced with a small dashboard that calls the protected API and displays:

  • the current authenticated Windows user;
  • the weather forecast returned by /api/weatherforecast.

Angular uses HttpClient with withCredentials: true for both protected API calls so browser credentials are included explicitly:

private readonly apiRequestOptions = { withCredentials: true } as const;

this.http.get<CurrentUser>('/api/user', this.apiRequestOptions);
this.http.get<WeatherForecast[]>('/api/weatherforecast', this.apiRequestOptions);

During development, Angular serves the SPA through the Angular dev server and proxies /api/** to the Aspire-managed Web API. The proxy reads the API HTTPS endpoint from the WEB_HTTPS environment variable provided by the AppHost reference:

const target = process.env['WEB_HTTPS'];

The proxy uses a keep-alive HTTP/HTTPS agent so the development proxy keeps connection affinity for Windows Authentication negotiation.

For production builds, Angular writes its output directly to src/Web/wwwroot, allowing the ASP.NET Core host to serve the SPA from the same origin as the API.

SPA Hosting and Security Headers

The Web API project contains an AngularExtensions helper that configures ASP.NET Core to serve the Angular production build and fall back to index.html for client-side routes:

app.UseAngularStaticFiles();

// API routes are mapped before the SPA fallback.
await app.RunWithAngularFallbackAsync();

The fallback also maps unmatched /api/** requests to 404 so missing API routes do not accidentally return the Angular shell.

Server-side static file responses add security headers such as X-Content-Type-Options, Referrer-Policy, Cross-Origin-Resource-Policy, and source map headers when matching .map files exist. The index.html response adds the stricter document-level headers, including:

  • Content-Security-Policy
  • Permissions-Policy
  • X-Frame-Options
  • Cross-Origin-Opener-Policy
  • Cross-Origin-Embedder-Policy

The Angular development server mirrors the same policy through angular.json serve.options.headers, so local development receives the same Content Security Policy and related browser security headers while running through ng serve.

The Angular production build configuration uses an empty browser output folder so the generated files are written directly into src/Web/wwwroot:

"outputPath": {
	"base": "../Web/wwwroot",
	"browser": ""
}

Setup Commands Run So Far

dotnet new sln -n AngularAspireNegotiate -f slnx

dotnet new aspire-apphost -n AppHost -o ./aspire/AppHost -lang C#
dotnet new aspire-servicedefaults -n ServiceDefaults -o ./aspire/ServiceDefaults -lang C#

dotnet new webapi -n Web -o ./src/Web -au windows -minimal

ng new web-angular --ai-config copilot --directory src/Web.Angular --package-manager npm --routing true --skip-git --skip-install --ssr false --style scss --zoneless true

@'
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.5171056">

	<PropertyGroup>
		<ShouldRunNpmInstall>false</ShouldRunNpmInstall>
		<ShouldRunBuildScript>false</ShouldRunBuildScript>
	</PropertyGroup>

</Project>
'@ | Set-Content -Path ./src/Web.Angular/Web.Angular.esproj -Encoding UTF8

dotnet sln AngularAspireNegotiate.slnx add ./aspire/AppHost/AppHost.csproj ./aspire/ServiceDefaults/ServiceDefaults.csproj --solution-folder aspire
dotnet sln AngularAspireNegotiate.slnx add ./src/Web/Web.csproj ./src/Web.Angular/Web.Angular.esproj --solution-folder src

aspire config set appHost.path ./aspire/AppHost/AppHost.csproj
aspire config set appHost.language csharp

aspire add javascript
dotnet add ./aspire/AppHost/AppHost.csproj reference ./src/Web/Web.csproj

New-Item -ItemType Directory -Force -Path ./.vscode
@'
{
	"json.schemas": [
		{
			"fileMatch": [
				"/aspire.config.json"
			],
			"url": "https://aspire.dev/reference/cli/configuration/schema.json"
		}
	]
}
'@ | Set-Content -Path ./.vscode/settings.json -Encoding UTF8

aspire agent init

About

.Net Web Api project hosting an Angular SPA with negotian auth with Aspire orchestration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors