Skip to content
  •  
  •  
  •  
53 changes: 53 additions & 0 deletions 1-web-apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 🌐 Web App Samples

This folder contains a collection of .NET web application samples demonstrating authentication and authorization scenarios using the Microsoft identity platform. Each sample showcases a different web application type or authentication flow, using up-to-date libraries and best practices.

## 📋 Samples Overview

| 📁 Folder Name | 🔑 Authentication Libraries Used | 🏷️ .NET Version |
|-----------------------------------------------------|--------------------------------------------------------------|------------------|
| [web-app-aspnet-core](./web-app-aspnetcore) | Microsoft.Identity.Web, Microsoft.Identity.Client (MSAL.NET) | .NET 8.0 |
| [web-app-blazor-server](./web-app-blazor-server) | Microsoft.Identity.Web, Microsoft.Identity.Client (MSAL.NET) | .NET 8.0 |

> [!NOTE]
> All samples use the latest supported versions of the Microsoft identity libraries and are configured for secure, modern authentication scenarios.

---

## 🚀 Getting Started

Follow these steps to set up your environment and run any of the web app samples in this folder.

### ☑️ Prerequisites

You will need the following to run any of these samples:

- [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
- A Microsoft Entra tenant and app registration (see each sample's README for details)
- An editor or IDE such as [Visual Studio](https://visualstudio.microsoft.com/) or [Visual Studio Code](https://code.visualstudio.com/)

### 📥 Clone the Repository

1. Navigate to where you want to have the sample located, and enter the following:

```sh
git clone https://github.com/your-org/ms-identity-docs-code-dotnet.git
```

2. Navigate to the web app folder in the sample you have downloaded by using the following command;

```sh
cd ms-identity-docs-code-dotnet/1-web-apps
```

---

## 📚 Resources

- [Microsoft Identity Platform Documentation](https://learn.microsoft.com/entra/identity-platform/)
- [Microsoft.Identity.Web Library](https://learn.microsoft.com/entra/msal/dotnet/microsoft-identity-web/)
- [MSAL.NET Library](https://learn.microsoft.com/entra/identity-platform/msal-overview)
- [Microsoft Entra App Registration](https://learn.microsoft.com/entra/identity-platform/quickstart-register-app)
- [Securing ASP.NET Core with Microsoft Identity](https://learn.microsoft.com/aspnet/core/security/authentication/identity)

---
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace sign_in_webapp.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace sign_in_webapp.Pages;

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

private readonly ILogger<ErrorModel> _logger;

public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}

public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
@* <ms_docref_add_api_result_display> *@
<p>Before rendering the page, the Page Model was able to make a call to Microsoft Graph's <code>/me</code> API for your user and received the following:</p>
<p><pre><code class="language-js">@ViewData["ApiResult"]</code></pre></p>
<p>Refreshing this page will continue to use the cached access token acquired for Microsoft Graph, which is valid for future page views will attempt to refresh this token as it nears its expiration.</p>
@* </ms_docref_add_api_result_display> *@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

@* <ms_docref_add_api_result_display> *@
<p>Before rendering the page, the Page Model was able to make a call to Microsoft Graph's <code>/me</code> API for your user and received the following:</p>

<p><pre><code class="language-js">@ViewData["ApiResult"]</code></pre></p>

<p>Refreshing this page will continue to use the cached access token acquired for Microsoft Graph, which is valid for future page views will attempt to refresh this token as it nears its expiration.</p>
@* </ms_docref_add_api_result_display> *@
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;
namespace sign_in_webapp.Pages;
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MicrosoftGraph:Scopes")]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IDownstreamApi _downstreamWebApi;
public IndexModel(ILogger<IndexModel> logger,
IDownstreamApi downstreamWebApi)
{
_logger = logger;
_downstreamWebApi = downstreamWebApi;
}
public async Task OnGet()
{
using var response = await _downstreamWebApi.CallApiForUserAsync("MicrosoftGraph").ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiResult = await response.Content.ReadFromJsonAsync<JsonDocument>().ConfigureAwait(false);
ViewData["ApiResult"] = JsonSerializer.Serialize(apiResult, new JsonSerializerOptions { WriteIndented = true });
}
else
{
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}");
}
}
}
using System.Text.Json;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

namespace sign_in_webapp.Pages;

[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MicrosoftGraph:Scopes")]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;

private readonly IDownstreamApi _downstreamWebApi;

public IndexModel(ILogger<IndexModel> logger,
IDownstreamApi downstreamWebApi)
{
_logger = logger;
_downstreamWebApi = downstreamWebApi;
}

public async Task OnGet()
{
using var response = await _downstreamWebApi.CallApiForUserAsync("MicrosoftGraph").ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiResult = await response.Content.ReadFromJsonAsync<JsonDocument>().ConfigureAwait(false);
ViewData["ApiResult"] = JsonSerializer.Serialize(apiResult, new JsonSerializerOptions { WriteIndented = true });
}
else
{
var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy.</p>
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace sign_in_webapp.Pages;
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace sign_in_webapp.Pages;

public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}

public void OnGet()
{
}
}

Loading