Skip to content

Commit 27521d4

Browse files
committed
Update easyauth page
1 parent 0de7e1d commit 27521d4

File tree

9 files changed

+152
-66
lines changed

9 files changed

+152
-66
lines changed

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Components/Pages/EasyAuthLogin.razor

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
@page "/.auth/login/{identityProvider}"
22
@inject IEasyAuthService EasyAuthService
3+
@inject IHttpContextAccessor HttpContextAccessor
34
@inject NavigationManager NavigationManager
5+
@inject ProtectedSessionStorage ProtectedSessionStorage
46
@rendermode @(new InteractiveServerRenderMode(prerender: false))
57

68
<div class="container mt-5">
@@ -12,24 +14,43 @@
1214
<div class="row mb-3">
1315
<label class="form-label col-2 text-end" for="authentication-type">Provider</label>
1416
<div class="col-10">
15-
<input type="text" class="form-control" id="authentication-type" placeholder="Choose an auth provider" value="@IdentityProvider" disabled>
17+
<input type="text" class="form-control" id="authentication-type" placeholder="Choose an auth provider" value="@IdentityProvider" disabled required>
1618
<small class="form-text text-muted">Name of the identity provider</small>
1719
</div>
1820
</div>
1921

22+
@if (this.IdentityProviderType == IdentityProviderType.EntraID)
23+
{
24+
<div class="row mb-3">
25+
<label class="form-label col-2 text-end" for="tenant-id">Tenant ID</label>
26+
<div class="col-10">
27+
<input type="text" class="form-control" id="tenant-id" placeholder="Enter a tenant ID" @bind="@TenantId" required>
28+
<small class="form-text text-muted">A tenant ID</small>
29+
</div>
30+
</div>
31+
}
32+
33+
<div class="row mb-3">
34+
<label class="form-label col-2 text-end" for="client-id">Client ID</label>
35+
<div class="col-10">
36+
<input type="text" class="form-control" id="client-id" placeholder="Enter a client ID" @bind="@ClientId" required>
37+
<small class="form-text text-muted">A client ID</small>
38+
</div>
39+
</div>
40+
2041
<div class="row mb-3">
2142
<label class="form-label col-2 text-end" for="user-id">User ID</label>
2243
<div class="col-10">
23-
<input type="text" class="form-control" id="user-id" placeholder="Choose a user ID" @bind="@UserId">
24-
<small class="form-text text-muted">An Azure EasyAuth-specific unique ID for the user</small>
44+
<input type="text" class="form-control" id="user-id" placeholder="Choose a user ID" @bind="@UserId" required>
45+
<small class="form-text text-muted">A user ID</small>
2546
</div>
2647
</div>
2748

2849
<div class="row mb-3">
2950
<label class="form-label col-2 text-end" for="username">Username</label>
3051
<div class="col-10">
31-
<input type="text" class="form-control" id="username" placeholder="Choose a username" @bind="@Username">
32-
<small class="form-text text-muted">Username or email address of the user</small>
52+
<input type="text" class="form-control" id="username" placeholder="Choose a username" @bind="@Username" required>
53+
<small class="form-text text-muted">Username or email address</small>
3354
</div>
3455
</div>
3556

@@ -45,7 +66,7 @@
4566
<div class="row mb-3">
4667
<label class="form-label col-2 text-end" for="user-claims">User's claims</label>
4768
<div class="col-10">
48-
<textarea class="form-control" id="user-claims" rows="10" placeholder="[{'typ': 'name','val': 'Azure EasyAuth'}]" @bind="@UserClaims"></textarea>
69+
<textarea class="form-control" id="user-claims" rows="6" placeholder="[{'typ': 'name','val': 'Azure EasyAuth'}]" @bind="@UserClaims"></textarea>
4970
<small class="form-text text-muted">Claims from the identity provider. JSON array of claims.</small>
5071
</div>
5172
</div>
@@ -64,6 +85,9 @@
6485
<Footer />
6586

6687
@code {
88+
private IdentityProviderType IdentityProviderType { get; set; }
89+
private string? TenantId { get; set; }
90+
private string? ClientId { get; set; }
6791
private string? UserId { get; set; }
6892
private string? Username { get; set; }
6993
private string? UserRoles { get; set; }
@@ -72,37 +96,46 @@
7296
[Parameter]
7397
public string? IdentityProvider { get; set; }
7498

75-
[CascadingParameter]
76-
private HttpContext? HttpContext { get; set; }
77-
7899
protected override async Task OnInitializedAsync()
79100
{
80-
this.UserId = this.EasyAuthService.UserId.ToString("N");
101+
this.IdentityProviderType = this.EasyAuthService.GetIdentityProvider(this.IdentityProvider);
102+
this.TenantId = this.EasyAuthService.TenantId.ToString();
103+
this.ClientId = this.EasyAuthService.ClientId.ToString();
104+
this.UserId = this.EasyAuthService.UserId.ToString();
81105
this.Username = string.Empty;
82106
this.UserRoles = this.ParseUserRoles(this.EasyAuthService.UserRoles);
83-
this.UserClaims = this.ParseUserClaims(await this.EasyAuthService.GetUserClaims(this.IdentityProvider));
107+
this.UserClaims = this.ParseUserClaims(await this.EasyAuthService.GetDefaultUserClaims(this.IdentityProvider));
84108

85109
await Task.CompletedTask;
86110
}
87111

88112
protected async Task LoginAsync()
89113
{
90-
var signedIn = await this.EasyAuthService.UserSignInAsync(this.HttpContext, this.IdentityProvider, this.UserId, this.Username, this.UserRoles, this.UserClaims);
114+
var signedIn = false;
115+
try
116+
{
117+
var signInContext = new UserSignInContext(this.IdentityProvider, this.TenantId, this.ClientId, this.UserId, this.Username, this.UserRoles, this.UserClaims);
118+
signedIn = await this.EasyAuthService.UserSignInAsync(this.ProtectedSessionStorage, signInContext);
119+
}
120+
catch (Exception ex)
121+
{
122+
Console.WriteLine(ex.Message);
123+
124+
return;
125+
}
91126

92127
if (signedIn)
93128
{
94129
this.NavigationManager.NavigateTo("/.auth/login/done");
95130
}
96-
97-
await Task.CompletedTask;
98131
}
99132

100133
protected async Task ClearAsync()
101134
{
102135
this.UserId = this.EasyAuthService.UserId.ToString("N");
103136
this.Username = string.Empty;
104137
this.UserRoles = this.ParseUserRoles(this.EasyAuthService.UserRoles);
105-
this.UserClaims = this.ParseUserClaims(await this.EasyAuthService.GetUserClaims(this.IdentityProvider));
138+
this.UserClaims = this.ParseUserClaims(await this.EasyAuthService.GetDefaultUserClaims(this.IdentityProvider));
106139

107140
await Task.CompletedTask;
108141
}

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Components/UI/EasyAuthResult.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
<div class="container mt-5">
1+
@inject IHttpContextAccessor HttpContextAccessor
2+
@inject NavigationManager NavigationManager
3+
4+
<div class="container mt-5">
25
<div class="card shadow-sm">
36
<div class="card-header bg-primary text-white">
47
<h3>@header</h3>

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Components/_Imports.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@using System.Text.Json
44
@using Microsoft.AspNetCore.Components.Forms
55
@using Microsoft.AspNetCore.Components.Routing
6+
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
67
@using Microsoft.AspNetCore.Components.Web
78
@using static Microsoft.AspNetCore.Components.Web.RenderMode
89
@using Microsoft.AspNetCore.Components.Web.Virtualization

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Models/IdentityProviderType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ public enum IdentityProviderType
1010
Google,
1111
OpenIDConnect,
1212
Twitter,
13-
}
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace Aliencube.Azure.Extensions.EasyAuth.Emulator.Models;
2+
3+
public record UserSignInContext(string? IdentityProvider, string? TenantId, string? ClientId, string? UserId, string? Username, string? UserRoles, string? UserClaims);

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Program.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77
builder.Services.AddRazorComponents()
88
.AddInteractiveServerComponents();
99

10-
builder.Services.AddSingleton<UserIdGenerator>();
10+
// builder.Services.AddDistributedMemoryCache();
11+
// builder.Services.AddSession(options =>
12+
// {
13+
// options.IdleTimeout = TimeSpan.FromMinutes(30);
14+
// options.Cookie.HttpOnly = true;
15+
// options.Cookie.IsEssential = true;
16+
// });
17+
builder.Services.AddHttpContextAccessor();
18+
19+
builder.Services.AddSingleton<IdGenerator>();
1120
builder.Services.AddScoped<IEasyAuthService, EasyAuthService>();
1221

1322
var app = builder.Build();
@@ -25,6 +34,9 @@
2534
app.UseAntiforgery();
2635

2736
app.MapStaticAssets();
37+
38+
// app.UseSession();
39+
2840
app.MapRazorComponents<App>()
2941
.AddInteractiveServerRenderMode();
3042

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Services/EasyAuthService.cs

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using Aliencube.Azure.Extensions.EasyAuth.Emulator.Models;
55

6+
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
7+
68
namespace Aliencube.Azure.Extensions.EasyAuth.Emulator.Services;
79

810
public interface IEasyAuthService
@@ -11,76 +13,119 @@ public interface IEasyAuthService
1113

1214
Guid UserId { get; }
1315

16+
Guid ClientId { get; }
17+
18+
Guid TenantId { get; }
19+
1420
IEnumerable<string> UserRoles { get; }
1521

16-
Task<IEnumerable<MsClientPrincipalClaim>> GetUserClaims(string? identityProvider);
22+
IdentityProviderType GetIdentityProvider(string? identityProvider);
1723

18-
Task<bool> UserSignInAsync(HttpContext? context, string? identityProvider, string? userId, string? username, string? userRoles, string? userClaims);
24+
Task<IEnumerable<MsClientPrincipalClaim>> GetDefaultUserClaims(string? identityProvider);
25+
26+
Task<bool> UserSignInAsync(ProtectedSessionStorage session, UserSignInContext signInContext);
1927
}
2028

21-
public class EasyAuthService(UserIdGenerator userId) : IEasyAuthService
29+
public class EasyAuthService(IHttpContextAccessor accessor, IdGenerator generator) : IEasyAuthService
2230
{
31+
private readonly HttpContext? _context = accessor.HttpContext;
32+
2333
public JsonSerializerOptions JsonSerializerOptions { get; } = new() { WriteIndented = true };
2434

25-
public Guid UserId { get; } = userId.Value;
35+
public Guid UserId { get; } = generator.UserId;
36+
37+
public Guid ClientId { get; } = generator.ClientId;
38+
39+
public Guid TenantId { get; } = generator.TenantId;
2640

2741
public IEnumerable<string> UserRoles { get; } = [ "User", "Admin" ];
2842

29-
public async Task<IEnumerable<MsClientPrincipalClaim>> GetUserClaims(string? identityProvider)
43+
public IdentityProviderType GetIdentityProvider(string? identityProvider)
44+
{
45+
if (string.IsNullOrWhiteSpace(identityProvider) == true)
46+
{
47+
return IdentityProviderType.None;
48+
}
49+
50+
if (identityProvider.Equals("aad", StringComparison.InvariantCultureIgnoreCase))
51+
{
52+
return IdentityProviderType.EntraID;
53+
}
54+
55+
return Enum.TryParse<IdentityProviderType>(identityProvider, true, out var result) ? result : IdentityProviderType.None;
56+
}
57+
58+
public async Task<IEnumerable<MsClientPrincipalClaim>> GetDefaultUserClaims(string? identityProvider)
3059
{
31-
var claims = new List<MsClientPrincipalClaim>()
60+
List<MsClientPrincipalClaim>? claims;
61+
var provider = this.GetIdentityProvider(identityProvider);
62+
if (provider == IdentityProviderType.None)
63+
{
64+
claims = [];
65+
return await Task.FromResult(claims).ConfigureAwait(false);
66+
}
67+
68+
claims = provider switch
3269
{
33-
new() { Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Value = this.UserId.ToString() },
34-
new() { Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "User" },
35-
new() { Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "Admin" },
70+
IdentityProviderType.EntraID =>
71+
[
72+
new() { Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/objectidentifier", Value = this.UserId.ToString() },
73+
new() { Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "User" },
74+
new() { Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "Admin" },
75+
],
76+
IdentityProviderType.GitHub => [],
77+
_ => [],
3678
};
3779

3880
return await Task.FromResult(claims).ConfigureAwait(false);
3981
}
4082

41-
public async Task<bool> UserSignInAsync(HttpContext? context, string? identityProvider, string? userId, string? username, string? userRoles, string? userClaims)
83+
public async Task<bool> UserSignInAsync(ProtectedSessionStorage session, UserSignInContext signInContext)
4284
{
43-
var provider = ParseIdentityProvider(identityProvider);
85+
var provider = this.GetIdentityProvider(signInContext.IdentityProvider);
4486
if (provider == IdentityProviderType.None)
4587
{
4688
return await Task.FromResult(false).ConfigureAwait(false);
4789
}
4890

4991
var clientPrincipal = provider switch
5092
{
51-
IdentityProviderType.EntraID => this.BuildEntraIDMsClientPrincipal(identityProvider, userId, username, userRoles, userClaims),
52-
IdentityProviderType.GitHub => this.BuildGitHubMsClientPrincipal(identityProvider, userId, username, userRoles, userClaims),
93+
IdentityProviderType.EntraID => this.BuildEntraIDMsClientPrincipal(signInContext),
94+
IdentityProviderType.GitHub => this.BuildGitHubMsClientPrincipal(signInContext),
5395
_ => null,
5496
};
5597

56-
context!.Response.Headers.Append("X-MS-CLIENT-PRINCIPAL-NAME", username!);
57-
context!.Response.Headers.Append("X-MS-CLIENT-PRINCIPAL-ID", Guid.NewGuid().ToString());
58-
context!.Response.Headers.Append("X-MS-CLIENT-PRINCIPAL-IDP", identityProvider!);
59-
6098
var serialised = JsonSerializer.Serialize(clientPrincipal, JsonSerializerOptions);
6199
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(serialised));
62-
context!.Response.Headers.Append("X-MS-CLIENT-PRINCIPAL", encoded);
100+
var principal = new Dictionary<string, string>()
101+
{
102+
{ "X-MS-CLIENT-PRINCIPAL-NAME", signInContext.Username! },
103+
{ "X-MS-CLIENT-PRINCIPAL-ID", signInContext.UserId!.ToString() },
104+
{ "X-MS-CLIENT-PRINCIPAL-IDP", signInContext.IdentityProvider! },
105+
{ "X-MS-CLIENT-PRINCIPAL", encoded },
106+
};
107+
await session.SetAsync("easyauth", principal).ConfigureAwait(false);
63108

64109
return await Task.FromResult(true).ConfigureAwait(false);
65110
}
66111

67-
private MsClientPrincipal BuildEntraIDMsClientPrincipal(string? identityProvider, string? userId, string? username, string? userRoles, string? userClaims)
112+
private MsClientPrincipal BuildEntraIDMsClientPrincipal(UserSignInContext context)
68113
{
69114
var utcNow = DateTimeOffset.UtcNow;
70115
var defaultClaims = new List<MsClientPrincipalClaim>()
71116
{
72-
new() { Type = "aud", Value = userId },
73-
new() { Type = "iss", Value = "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0" },
117+
new() { Type = "aud", Value = context.ClientId },
118+
new() { Type = "iss", Value = $"https://login.microsoftonline.com/{context.TenantId}/v2.0" },
74119
new() { Type = "iat", Value = $"{utcNow.ToUnixTimeSeconds()}" },
75120
new() { Type = "nbf", Value = $"{utcNow.ToUnixTimeSeconds()}" },
76121
new() { Type = "nbf", Value = $"{utcNow.AddMinutes(90).ToUnixTimeSeconds()}" },
77122
new() { Type = "aio", Value = $"{Convert.ToBase64String(Guid.NewGuid().ToByteArray())}" },
78-
new() { Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value = username },
79-
new() { Type = "http://schemas.microsoft.com/identity/claims/identityprovider", Value = "https://sts.windows.net/00000000-0000-0000-0000-000000000000/" },
123+
new() { Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value = context.Username },
124+
new() { Type = "http://schemas.microsoft.com/identity/claims/identityprovider", Value = $"https://sts.windows.net/{context.TenantId}/" },
80125
};
81126

82-
var userRoleClaims = (userRoles?.Split('\n', StringSplitOptions.RemoveEmptyEntries) ?? []).Select(p => new MsClientPrincipalClaim() { Type = "roles", Value = p });
83-
var userClaimClaims = JsonSerializer.Deserialize<IEnumerable<MsClientPrincipalClaim>>(userClaims ?? "[]", JsonSerializerOptions) ?? [];
127+
var userRoleClaims = (context.UserRoles?.Split('\n', StringSplitOptions.RemoveEmptyEntries) ?? []).Select(p => new MsClientPrincipalClaim() { Type = "roles", Value = p });
128+
var userClaimClaims = JsonSerializer.Deserialize<IEnumerable<MsClientPrincipalClaim>>(context.UserClaims ?? "[]", JsonSerializerOptions) ?? [];
84129

85130
var claims = new List<MsClientPrincipalClaim>();
86131
claims.AddRange(defaultClaims);
@@ -95,7 +140,7 @@ private MsClientPrincipal BuildEntraIDMsClientPrincipal(string? identityProvider
95140
}
96141
var principal = new MsClientPrincipal()
97142
{
98-
IdentityProvider = identityProvider,
143+
IdentityProvider = context.IdentityProvider,
99144
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
100145
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
101146
Claims = claims,
@@ -104,23 +149,8 @@ private MsClientPrincipal BuildEntraIDMsClientPrincipal(string? identityProvider
104149
return principal;
105150
}
106151

107-
private MsClientPrincipal BuildGitHubMsClientPrincipal(string? identityProvider, string? userId, string? username, string? userRoles, string? userClaims)
152+
private MsClientPrincipal BuildGitHubMsClientPrincipal(UserSignInContext context)
108153
{
109154
throw new NotImplementedException();
110155
}
111-
112-
private static IdentityProviderType ParseIdentityProvider(string? identityProvider)
113-
{
114-
if (string.IsNullOrWhiteSpace(identityProvider) == true)
115-
{
116-
return IdentityProviderType.None;
117-
}
118-
119-
if (identityProvider.Equals("aad", StringComparison.InvariantCultureIgnoreCase))
120-
{
121-
return IdentityProviderType.EntraID;
122-
}
123-
124-
return Enum.TryParse<IdentityProviderType>(identityProvider, true, out var result) ? result : IdentityProviderType.None;
125-
}
126156
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Aliencube.Azure.Extensions.EasyAuth.Emulator.Services;
2+
3+
public class IdGenerator
4+
{
5+
public Guid UserId { get; } = Guid.NewGuid();
6+
7+
public Guid ClientId { get; } = Guid.NewGuid();
8+
9+
public Guid TenantId { get; } = Guid.NewGuid();
10+
}

src/Aliencube.Azure.Extensions.EasyAuth.Emulator/Services/UserIdGenerator.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)