ソース情報
- リポジトリ
- tomevault-io/tomes
- ソースの最終更新活動
- 2026年7月23日 21:48
- 検出された SKILL.md の言語
- 英語
- スター
- 1
- フォーク
- 0
インストール方法
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
ソースファイルを確認
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
メニュー
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
直接コマンドでは確認用 Prompt が省略されます。実行前にソースを確認してください。
npx skills add https://github.com/tomevault-io/tomes --skill entra-id-aspire-authenticationコマンドは1行のまま表示されます。コピー前に横へスクロールして全体を確認してください。
ローカルで確認しますか?SkillsMP が現在取得できるファイルをダウンロードできます。
> Use when this capability is needed.
Use when writing kernel, account, or note MASM code that reads from or writes to the advice provider (advice stack / advice map) — validate advice data.
Use when writing a Rust test that exercises a failure path or a MASM test that expects a `panic` / `assert` — assert on the specific expected error variant or error code.
SOC 職業分類に基づく
SKILL.md を表示中
| name | entra-id-aspire-authentication |
| description | | Use when this capability is needed. |
This skill helps you integrate Microsoft Entra ID (Azure AD) authentication into .NET Aspire distributed applications using Microsoft.Identity.Web.
User Browser → Blazor Server (OIDC) → Entra ID → Access Token → Protected API (JWT)
Key Components:
AddMicrosoftIdentityWebApp for OIDC + MicrosoftIdentityMessageHandler for token attachmentAddMicrosoftIdentityWebApi for JWT validationhttps+http://servicename URLsBefore starting, the agent MUST:
Scan each project's Program.cs to identify its type:
# Find all Program.cs files in solution
Get-ChildItem -Recurse -Filter "Program.cs" | ForEach-Object {
$content = Get-Content $_.FullName -Raw
$projectDir = Split-Path $_.FullName -Parent
$projectName = Split-Path $projectDir -Leaf
# Skip AppHost and ServiceDefaults
if ($projectName -match "AppHost|ServiceDefaults") { return }
$isWebApp = $content -match "AddRazorComponents|MapRazorComponents|AddServerSideBlazor"
$isApi = $content -match "MapGet|MapPost|MapPut|MapDelete|AddControllers"
if ($isWebApp) {
Write-Host "WEB APP: $projectName (has Razor/Blazor components)"
} elseif ($isApi) {
Write-Host "API: $projectName (exposes endpoints)"
}
}
Detection rules:
Pattern in Program.cs | Project Type |
|---|---|
AddRazorComponents / MapRazorComponents / AddServerSideBlazor | Blazor Web App |
MapGet / MapPost / AddControllers (without Razor) | Web API |
Note: APIs can call other APIs (downstream). The Aspire
.WithReference()shows service dependencies, not necessarily web-to-API relationships.
AGENT: Show detected topology and ask for confirmation:
"I detected:
- Web App (Blazor):
{webProjectName}- API:
{apiProjectName}The web app will authenticate users and call the API. Is this correct?"
AGENT: Explain the two-phase approach:
"I'll implement authentication in two phases:
Phase 1 (now): Add authentication code with placeholder values. The app will build but won't run until app registrations are configured.
Phase 2 (after): Use the
entra-id-aspire-provisioningskill to create Entra ID app registrations and update the configuration with real values.Ready to proceed with Phase 1?"
CRITICAL: Complete ALL steps in order. Do not skip any step.
Two-phase workflow:
- Phase 1: Add authentication code with placeholder values → App will build but not run
- Phase 2: Run
entra-id-aspire-provisioningskill to create app registrations → App will run
1.1 Add Package:
cd MyService.ApiService
dotnet add package Microsoft.Identity.Web
1.2 Configure appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<your-tenant-id>",
"ClientId": "<your-api-client-id>",
"Audiences": ["api://<your-api-client-id>"]
}
}
1.3 Update Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Add JWT Bearer authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Protect endpoints
app.MapGet("/weatherforecast", () => { /* ... */ })
.RequireAuthorization();
app.Run();
2.1 Add Package:
cd MyService.Web
dotnet add package Microsoft.Identity.Web
2.2 Configure appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<your-tenant>.onmicrosoft.com",
"TenantId": "<tenant-guid>",
"ClientId": "<web-app-client-id>",
"CallbackPath": "/signin-oidc",
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret": "<your-client-secret>"
}
]
},
"WeatherApi": {
"Scopes": ["api://<api-client-id>/.default"]
}
}
2.3 Update Program.cs:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Authentication + token acquisition
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
// Add Blazor authentication challenge handler for incremental consent and Conditional Access
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();
// HttpClient with automatic token attachment
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new("https+http://apiservice"); // Aspire service discovery
})
.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi").Bind);
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
app.MapGroup("/authentication").MapLoginAndLogout();
app.Run();
2.4 Blazor authentication helpers:
These helpers ship in the Microsoft.Identity.Web NuGet package (version 3.3.0+). Simply add using Microsoft.Identity.Web; — no file copying required.
AGENT: The
LoginLogoutEndpointRouteBuilderExtensionsclass provides theMapLoginAndLogout()extension method with support for incremental consent and Conditional Access. TheBlazorAuthenticationChallengeHandlerclass handles authentication challenges in Blazor Server components. Both are now included in Microsoft.Identity.Web and are automatically available once you reference the package.
2.6 Create UserInfo Component (Components/UserInfo.razor) — THE LOGIN BUTTON:
CRITICAL: This step is frequently forgotten. Without this, users have no way to log in!
@using Microsoft.AspNetCore.Components.Authorization
<AuthorizeView>
<Authorized>
<span class="nav-item">Hello, @context.User.Identity?.Name</span>
<form action="/authentication/logout" method="post" class="nav-item">
<AntiforgeryToken />
<input type="hidden" name="returnUrl" value="/" />
<button type="submit" class="btn btn-link nav-link">Logout</button>
</form>
</Authorized>
<NotAuthorized>
<a href="/authentication/login?returnUrl=/" class="nav-link">Login</a>
</NotAuthorized>
</AuthorizeView>
2.7 Update MainLayout.razor to include UserInfo:
Find the <main> or navigation section in Components/Layout/MainLayout.razor and add the UserInfo component:
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<UserInfo /> @* <-- ADD THIS LINE *@
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
2.8 Update Routes.razor for AuthorizeRouteView:
Replace RouteView with AuthorizeRouteView in Components/Routes.razor:
@using Microsoft.AspNetCore.Components.Authorization
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
<NotAuthorized>
<p>You are not authorized to view this page.</p>
<a href="/authentication/login">Login</a>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
2.9 Store Client Secret in User Secrets:
Never commit secrets to source control!
cd MyService.Web
dotnet user-secrets init
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "<your-client-secret>"
Then update appsettings.json to reference user secrets (remove the hardcoded secret):
{
"AzureAd": {
"ClientCredentials": [
{
// For more options see https://aka.ms/ms-id-web/credentials
"SourceType": "ClientSecret"
}
]
}
}
@page "/weather"
@attribute [Authorize]
app.MapGet("/weatherforecast", () => { /* ... */ })
.RequireAuthorization()
.RequireScope("access_as_user");
.AddMicrosoftIdentityMessageHandler(options =>
{
options.Scopes.Add("api://<api-client-id>/.default");
options.RequestAppToken = true;
});
var request = new HttpRequestMessage(HttpMethod.Get, "/endpoint")
.WithAuthenticationOptions(options =>
{
options.Scopes.Clear();
options.Scopes.Add("api://<client-id>/specific.scope");
});
{
"AzureAd": {
"ClientCredentials": [
{
"SourceType": "SignedAssertionFromManagedIdentity",
"ManagedIdentityClientId": "<user-assigned-mi-client-id>"
}
]
}
}
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
builder.Services.AddDownstreamApi("GraphApi", builder.Configuration.GetSection("GraphApi"));
This is NOT optional — Blazor Server requires explicit exception handling for Conditional Access and consent.
When calling APIs, Conditional Access policies or consent requirements can trigger MicrosoftIdentityWebChallengeUserException. You MUST handle this on every page that calls a downstream API.
Step 2.3 registers the handler — AddScoped<BlazorAuthenticationChallengeHandler>() makes the service available.
Each page calling APIs needs this pattern:
@page "/weather"
@attribute [Authorize]
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Identity.Web
@inject WeatherApiClient WeatherApi
@inject BlazorAuthenticationChallengeHandler ChallengeHandler
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-warning">@errorMessage</div>
}
else if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
@* Display your data *@
}
@code {
private WeatherForecast[]? forecasts;
private string? errorMessage;
protected override async Task OnInitializedAsync()
{
if (!await ChallengeHandler.IsAuthenticatedAsync())
{
// Not authenticated - redirect to login with required scopes
await ChallengeHandler.ChallengeUserWithConfiguredScopesAsync("WeatherApi:Scopes");
return;
}
try
{
forecasts = await WeatherApi.GetWeatherAsync();
}
catch (Exception ex)
{
// Handle incremental consent / Conditional Access
if (!await ChallengeHandler.HandleExceptionAsync(ex))
{
errorMessage = $"Error loading data: {ex.Message}";
}
}
}
}
Why this pattern?
IsAuthenticatedAsync()checks if user is signed in before making API callsHandleExceptionAsync()catchesMicrosoftIdentityWebChallengeUserException(or as InnerException)- If it is a challenge exception → redirects user to re-authenticate with required claims/scopes
- If it is NOT a challenge exception → returns false so you can handle the error
Why is this not automatic? Blazor Server's circuit-based architecture requires explicit handling. The handler re-challenges the user by navigating to the login endpoint with the required claims/scopes.
| Issue | Solution |
|---|---|
| 401 on API calls | Verify scopes match the API's App ID URI |
| OIDC redirect fails | Add /signin-oidc to Azure AD redirect URIs |
| Token not attached | Ensure AddMicrosoftIdentityMessageHandler is configured |
| AADSTS65001 | Admin consent required - grant in Azure Portal |
404 on /MicrosoftIdentity/Account/Challenge | Use BlazorAuthenticationChallengeHandler instead of MicrosoftIdentityConsentHandler |
| Project | File | Purpose |
|---|---|---|
| ApiService | Program.cs | JWT auth + RequireAuthorization() |
| ApiService | appsettings.json | AzureAd config (ClientId, TenantId) |
| Web | Program.cs | OIDC + token acquisition + challenge handler registration |
| Web | appsettings.json | AzureAd config + downstream API scopes |
| Web | Components/UserInfo.razor | Login/logout button UI |
| Web | Components/Layout/MainLayout.razor | Include UserInfo in layout |
| Web | Components/Routes.razor | AuthorizeRouteView for protected pages |
Note:
LoginLogoutEndpointRouteBuilderExtensionsandBlazorAuthenticationChallengeHandlerare now included in the Microsoft.Identity.Web NuGet package (v3.3.0+). Simply reference the package and useusing Microsoft.Identity.Web;— no file copying required.
AGENT: After completing all steps, verify:
Build succeeds:
dotnet build
Check all files were created/modified:
Program.cs has AddMicrosoftIdentityWebApiappsettings.json has AzureAd sectionProgram.cs has AddMicrosoftIdentityWebApp and AddMicrosoftIdentityMessageHandlerProgram.cs has AddScoped<BlazorAuthenticationChallengeHandler>()appsettings.json has AzureAd and scope configurationComponents/UserInfo.razor (LOGIN BUTTON)MainLayout.razor includes <UserInfo />Routes.razor uses AuthorizeRouteViewChallengeHandler.HandleExceptionAsync(ex)AGENT: Inform user of next step:
"✅ Phase 1 complete! Authentication code is in place. The app will build but won't run until app registrations are configured.
Next: Run the
entra-id-aspire-provisioningskill to:
- Create Entra ID app registrations
- Update
appsettings.jsonwith real ClientIds- Store client secret securely
Ready to proceed with provisioning?"
Source: AzureAD/microsoft-identity-web — distributed by TomeVault.