-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
60 changed files
with
3,039 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.0.31903.59 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dry.Server", "dry\Server\dry.Server.csproj", "{483238EB-F9FB-47F1-B598-992DA2437DA1}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dry.Shared", "dry\Shared\dry.Shared.csproj", "{B9617BE1-36FF-48F0-AA7C-FA73238245AC}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dry.Client", "dry\Client\dry.Client.csproj", "{32E73041-442D-4995-8ED5-447C86C7DB59}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{483238EB-F9FB-47F1-B598-992DA2437DA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{483238EB-F9FB-47F1-B598-992DA2437DA1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{483238EB-F9FB-47F1-B598-992DA2437DA1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{483238EB-F9FB-47F1-B598-992DA2437DA1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{B9617BE1-36FF-48F0-AA7C-FA73238245AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{B9617BE1-36FF-48F0-AA7C-FA73238245AC}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{B9617BE1-36FF-48F0-AA7C-FA73238245AC}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{B9617BE1-36FF-48F0-AA7C-FA73238245AC}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{32E73041-442D-4995-8ED5-447C86C7DB59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{32E73041-442D-4995-8ED5-447C86C7DB59}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{32E73041-442D-4995-8ED5-447C86C7DB59}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{32E73041-442D-4995-8ED5-447C86C7DB59}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {6DBF8473-06FC-43AF-A14F-41F17AB93C7E} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<CascadingAuthenticationState> | ||
<Router AppAssembly="@typeof(Program).Assembly"> | ||
<Found Context="routeData"> | ||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | ||
</Found> | ||
<NotFound> | ||
<LayoutView Layout="@typeof(MainLayout)"> | ||
<p>Sorry, there's nothing at this address.</p> | ||
</LayoutView> | ||
</NotFound> | ||
</Router> | ||
</CascadingAuthenticationState> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
@page "/directapi" | ||
@inject IAntiforgeryHttpClientFactory httpClientFactory | ||
@inject IJSRuntime JSRuntime | ||
|
||
<h1>Data from Direct API</h1> | ||
|
||
@if (apiData == null) | ||
{ | ||
<p><em>Loading...</em></p> | ||
} | ||
else | ||
{ | ||
<table class="table"> | ||
<thead> | ||
<tr> | ||
<th>Data</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
@foreach (var data in apiData) | ||
{ | ||
<tr> | ||
<td>@data</td> | ||
</tr> | ||
} | ||
</tbody> | ||
</table> | ||
} | ||
|
||
@code { | ||
private string[]? apiData; | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
var client = await httpClientFactory.CreateClientAsync(); | ||
|
||
apiData = await client.GetFromJsonAsync<string[]>("api/DirectApi"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
@page "/graphapicall" | ||
@inject IHttpClientFactory httpClientFactory | ||
@inject IJSRuntime JSRuntime | ||
|
||
<h1>Data from Graph API</h1> | ||
|
||
@if (apiData == null) | ||
{ | ||
<p><em>Loading...</em></p> | ||
} | ||
else | ||
{ | ||
<table class="table"> | ||
<thead> | ||
<tr> | ||
<th>Data</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
@foreach (var data in apiData) | ||
{ | ||
<tr> | ||
<td>@data</td> | ||
</tr> | ||
} | ||
</tbody> | ||
</table> | ||
} | ||
|
||
@code { | ||
private string[]? apiData; | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
var token = await JSRuntime.InvokeAsync<string>("getAntiForgeryToken"); | ||
|
||
var client = httpClientFactory.CreateClient("authorizedClient"); | ||
client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", token); | ||
|
||
apiData = await client.GetFromJsonAsync<string[]>("api/GraphApiCalls"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@page "/" | ||
|
||
<h1>Microsoft Entra ID using cookies</h1> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using dry.Client; | ||
using dry.Client.Services; | ||
|
||
using Microsoft.AspNetCore.Components.Authorization; | ||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
||
using System.Net.Http.Headers; | ||
|
||
var builder = WebAssemblyHostBuilder.CreateDefault(args); | ||
builder.Services.AddOptions(); | ||
builder.Services.AddAuthorizationCore(); | ||
builder.Services.TryAddSingleton<AuthenticationStateProvider, HostAuthenticationStateProvider>(); | ||
builder.Services.TryAddSingleton(sp => (HostAuthenticationStateProvider)sp.GetRequiredService<AuthenticationStateProvider>()); | ||
builder.Services.AddTransient<AuthorizedHandler>(); | ||
|
||
builder.RootComponents.Add<App>("#app"); | ||
|
||
builder.Services.AddHttpClient("default", client => | ||
{ | ||
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress); | ||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
}); | ||
|
||
builder.Services.AddHttpClient("authorizedClient", client => | ||
{ | ||
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress); | ||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); | ||
}).AddHttpMessageHandler<AuthorizedHandler>(); | ||
|
||
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("default")); | ||
builder.Services.AddTransient<IAntiforgeryHttpClientFactory, AntiforgeryHttpClientFactory>(); | ||
|
||
await builder.Build().RunAsync(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Microsoft.JSInterop; | ||
|
||
namespace dry.Client.Services; | ||
|
||
public class AntiforgeryHttpClientFactory : IAntiforgeryHttpClientFactory | ||
{ | ||
private readonly IHttpClientFactory _httpClientFactory; | ||
private readonly IJSRuntime _jSRuntime; | ||
|
||
public AntiforgeryHttpClientFactory(IHttpClientFactory httpClientFactory, IJSRuntime jSRuntime) | ||
{ | ||
_httpClientFactory = httpClientFactory; | ||
_jSRuntime = jSRuntime; | ||
} | ||
|
||
public async Task<HttpClient> CreateClientAsync(string clientName = "authorizedClient") | ||
{ | ||
var token = await _jSRuntime.InvokeAsync<string>("getAntiForgeryToken"); | ||
|
||
var client = _httpClientFactory.CreateClient(clientName); | ||
client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", token); | ||
|
||
return client; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System.Net; | ||
|
||
namespace dry.Client.Services; | ||
|
||
// orig src https://github.com/berhir/BlazorWebAssemblyCookieAuth | ||
public class AuthorizedHandler : DelegatingHandler | ||
{ | ||
private readonly HostAuthenticationStateProvider _authenticationStateProvider; | ||
|
||
public AuthorizedHandler(HostAuthenticationStateProvider authenticationStateProvider) | ||
{ | ||
_authenticationStateProvider = authenticationStateProvider; | ||
} | ||
|
||
protected override async Task<HttpResponseMessage> SendAsync( | ||
HttpRequestMessage request, | ||
CancellationToken cancellationToken) | ||
{ | ||
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync(); | ||
HttpResponseMessage responseMessage; | ||
if (authState.User.Identity!= null && !authState.User.Identity.IsAuthenticated) | ||
{ | ||
// if user is not authenticated, immediately set response status to 401 Unauthorized | ||
responseMessage = new HttpResponseMessage(HttpStatusCode.Unauthorized); | ||
} | ||
else | ||
{ | ||
responseMessage = await base.SendAsync(request, cancellationToken); | ||
} | ||
|
||
if (responseMessage.StatusCode == HttpStatusCode.Unauthorized) | ||
{ | ||
var content = await responseMessage.Content.ReadAsStringAsync(); | ||
|
||
// if server returned 401 Unauthorized, redirect to login page | ||
if (content != null && content.Contains("acr")) // CAE | ||
{ | ||
_authenticationStateProvider.CaeStepUp(content); | ||
} | ||
else // standard | ||
{ | ||
_authenticationStateProvider.SignIn(); | ||
} | ||
} | ||
|
||
return responseMessage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using dry.Shared.Authorization; | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.AspNetCore.Components.Authorization; | ||
using System.Net.Http.Json; | ||
using System.Security.Claims; | ||
|
||
namespace dry.Client.Services; | ||
|
||
// orig src https://github.com/berhir/BlazorWebAssemblyCookieAuth | ||
public class HostAuthenticationStateProvider : AuthenticationStateProvider | ||
{ | ||
private static readonly TimeSpan _userCacheRefreshInterval = TimeSpan.FromSeconds(60); | ||
|
||
private const string LogInPath = "api/Account/Login"; | ||
private const string LogOutPath = "api/Account/Logout"; | ||
|
||
private readonly NavigationManager _navigation; | ||
private readonly HttpClient _client; | ||
private readonly ILogger<HostAuthenticationStateProvider> _logger; | ||
|
||
private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); | ||
private ClaimsPrincipal _cachedUser = new(new ClaimsIdentity()); | ||
|
||
public HostAuthenticationStateProvider(NavigationManager navigation, HttpClient client, ILogger<HostAuthenticationStateProvider> logger) | ||
{ | ||
_navigation = navigation; | ||
_client = client; | ||
_logger = logger; | ||
} | ||
|
||
public override async Task<AuthenticationState> GetAuthenticationStateAsync() | ||
{ | ||
return new AuthenticationState(await GetUser(useCache: true)); | ||
} | ||
|
||
public void SignIn(string? customReturnUrl = null) | ||
{ | ||
var returnUrl = customReturnUrl != null ? _navigation.ToAbsoluteUri(customReturnUrl).ToString() : null; | ||
var encodedReturnUrl = Uri.EscapeDataString(returnUrl ?? _navigation.Uri); | ||
var logInUrl = _navigation.ToAbsoluteUri($"{LogInPath}?returnUrl={encodedReturnUrl}"); | ||
_navigation.NavigateTo(logInUrl.ToString(), true); | ||
} | ||
|
||
public void CaeStepUp(string claimsChallenge, string? customReturnUrl = null) | ||
{ | ||
var returnUrl = customReturnUrl != null ? _navigation.ToAbsoluteUri(customReturnUrl).ToString() : null; | ||
var encodedReturnUrl = Uri.EscapeDataString(returnUrl ?? _navigation.Uri); | ||
var logInUrl = _navigation.ToAbsoluteUri($"{LogInPath}?claimsChallenge={claimsChallenge}&returnUrl={encodedReturnUrl}"); | ||
_navigation.NavigateTo(logInUrl.ToString(), true); | ||
} | ||
|
||
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false) | ||
{ | ||
var now = DateTimeOffset.Now; | ||
if (useCache && now < _userLastCheck + _userCacheRefreshInterval) | ||
{ | ||
_logger.LogDebug("Taking user from cache"); | ||
return _cachedUser; | ||
} | ||
|
||
_logger.LogDebug("Fetching user"); | ||
_cachedUser = await FetchUser(); | ||
_userLastCheck = now; | ||
|
||
return _cachedUser; | ||
} | ||
|
||
private async Task<ClaimsPrincipal> FetchUser() | ||
{ | ||
UserInfo? user = null; | ||
|
||
try | ||
{ | ||
_logger.LogInformation("{clientBaseAddress}", _client.BaseAddress?.ToString()); | ||
user = await _client.GetFromJsonAsync<UserInfo>("api/User"); | ||
} | ||
catch (Exception exc) | ||
{ | ||
_logger.LogWarning(exc, "Fetching user failed."); | ||
} | ||
|
||
if (user == null || !user.IsAuthenticated) | ||
{ | ||
return new ClaimsPrincipal(new ClaimsIdentity()); | ||
} | ||
|
||
var identity = new ClaimsIdentity( | ||
nameof(HostAuthenticationStateProvider), | ||
user.NameClaimType, | ||
user.RoleClaimType); | ||
|
||
if (user.Claims != null) | ||
{ | ||
foreach (var claim in user.Claims) | ||
{ | ||
identity.AddClaim(new Claim(claim.Type, claim.Value)); | ||
} | ||
} | ||
|
||
return new ClaimsPrincipal(identity); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace dry.Client.Services; | ||
|
||
public interface IAntiforgeryHttpClientFactory | ||
{ | ||
Task<HttpClient> CreateClientAsync(string clientName = "authorizedClient"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@inject IJSRuntime JSRuntime | ||
|
||
<input type="hidden" id="__RequestVerificationToken" | ||
name="__RequestVerificationToken" value="@GetToken()"> | ||
|
||
@code { | ||
|
||
private string token = ""; | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
token = await JSRuntime.InvokeAsync<string>("getAntiForgeryToken"); | ||
} | ||
|
||
public string GetToken() | ||
{ | ||
return token; | ||
} | ||
|
||
} |
Oops, something went wrong.