Skip to content

Commit dd4c5ea

Browse files
authored
Presentation demo code (#1)
* Adding base template React SPA * Adding IdentityServer base template * Adding Web projects * Adding Client apps * Changing IS to use easier to demo in-mem config * Setting up login from web app * Adding AAD external connection * Adding identity login to blazor admin app * Adding multiple APIs with shared user list and admin client basic setup * Adding endpoint * API demo setup * Adding WPF login setup * Adding data from API to client * Adding API data and settings * Connecting APIs * Adding startup script * Adding notes list API call * Adding web app API calls * Adding YARP setup for blazor * Adding proxy call from blazor * Adding React SPA login and API call * Adding login and notes list to mobile client * Adding Auth0 as external * Setup of external API access to Auth0 secured API * Updating Auth * layout * Cleanup of API calls and token configs * SPA login updates * Demo setup * Token test request
1 parent 71c963d commit dd4c5ea

File tree

363 files changed

+167014
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

363 files changed

+167014
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,4 @@ MigrationBackup/
348348

349349
# Ionide (cross platform F# VS Code tools) working folder
350350
.ionide/
351+
/**/.cr/personal/FavoritesList/List.xml

Requests.rest

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@identityUrl = https://localhost:5001
2+
@m2mClientId = m2m.api
3+
@m2mClientSecret = 511536EF-F270-4058-80CA-1C89C192F69A
4+
5+
###
6+
# @name ccToken
7+
POST {{identityUrl}}/connect/token
8+
Content-Type: application/x-www-form-urlencoded
9+
10+
client_id={{m2mClientId}}&client_secret={{m2mClientSecret}}&grant_type=client_credentials&scope=read:username
11+
12+
###
13+
@userToken = eyJhbGciOiJSUzI1NiIsImtpZCI6IjQ2ODBFQkFGQzYwRjA1OTc2NEIyQTlCMDlGRjQzMzREIiwidHlwIjoiYXQrand0In0.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo1MDAxIiwibmJmIjoxNjY3NDE0ODU3LCJpYXQiOjE2Njc0MTQ4NTcsImV4cCI6MTY2NzQxODQ1NywiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMS9yZXNvdXJjZXMiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiXSwiYW1yIjpbInB3ZCJdLCJjbGllbnRfaWQiOiJ3ZWItdWkiLCJzdWIiOiIyIiwiYXV0aF90aW1lIjoxNjY3NDE0ODU3LCJpZHAiOiJsb2NhbCIsInNpZCI6IkJCOUU0Mjk4RDFCNDQ1ODgzN0I0RkNGODIxQTlDOEYxIiwianRpIjoiRjFEQUExNTg4Q0RGMzU0OTZFOTNEQTczNjA3NkY0OTgifQ.Bn-D08RGt1nzZbAz1wcuFqdr7lTsPcG3_lkdi0Z3HuN4k0w75Jg-ioAnUjUkgBHV5Fan9jEnd1QZyokN1Hp_fUxyUx_ekicJNMd35R-wJfYYzq9fipbo1-9L7cA47GzdCRv_8wvMd97jj8Sxjx17m5QppLG3lGTuJwrR_G2JejjQK19vzAsII4dpKYdqtXwcsT1SWUnn7yCZGXag1TvgFU3BynPs4lALn31lw_kslnsGS4boHruCGUb1KZOWqcxyBb4rY7aljX2HGPOtjabv8T1-V1-WgpsLG1uOXAnXHswuwn_sYJ5p3cn36VpeqzEXfyKSVLUNnrQY00RlsahxPQ
14+
POST {{identityUrl}}/connect/userinfo
15+
Authorization: Bearer {{userToken}}
16+
17+
###
18+
@externalToken = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlNMd0NkZWhiN00tM0lIQUgzdG5RQiJ9.eyJpc3MiOiJodHRwczovL2Rldi10dWJob3l3ZzZhMHUzemNvLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw2MzdiMTU4MDliOWM0ZmVmZmJmNmUwOWEiLCJhdWQiOlsiaHR0cHM6Ly9kZW1vLnBhcnRuZXIuZXh0ZXJuYWwubG9jYWwiLCJodHRwczovL2Rldi10dWJob3l3ZzZhMHUzemNvLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE2Njk3NjA1NzksImV4cCI6MTY2OTg0Njk3OSwiYXpwIjoiS2ZBd3hRZTFhaVJ5QlFoTTZQVFBHMDBxa2loYWliS3AiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIHJlYWQ6Y2FsZW5kYXIifQ.gSHjcbGCYT5lvVQRO1FdOasoEbOLQ-za9BNEsN_yv8iM0zlWIaruwEYfmpn6gxHApv6A9oxMAwfAEXk-QJC0dTpquQ6Uy0yNbT-LoQtR2FZ0o37M9BEMlSyTNzNqQfStamkwG-cEsHrLc2s8hb751DhxbsuTLjffOjVR2FFeJBI6FUsEw8tHqI0h5evbjF0vtSrADM9FcWBh9Qs6uEowwOJRcqlzm2nRTqa620Le1B9o184DghmyTm5i8GLwPOi06xFk0g6Kagsy82FF4MdNm7Bxwz79JB3LY0uwf6yK5bjwnCIuXzcetNuipcrALKBlM5UoN1oOyGREqWW9OSiYtw
19+
GET https://localhost:7217/Calendar
20+
Authorization: Bearer {{externalToken}}
21+
22+
###
23+
POST {{identityUrl}}/connect/token
24+
Content-Type: application/x-www-form-urlencoded
25+
26+
client_id=m2m.web-admin&client_secret=7954d1dd-49c0-4b9e-acd9-780c78a5570e&grant_type=client_credentials&scope=read:users
27+

StartAll.ps1

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Start-Process dotnet -ArgumentList 'run --project ".\src\Identity\IdentityServer\IdentityServer.csproj"'
2+
Start-Sleep -Seconds 2
3+
4+
Start-Process dotnet -ArgumentList 'run --project ".\src\Web\Demo.Notes.Web\Demo.Partner.ExternalApi.Host\Demo.Partner.ExternalApi.Host.csproj"'
5+
Start-Sleep -Seconds 2
6+
Start-Process dotnet -ArgumentList 'run --project ".\src\Web\Demo.Notes.Web\Demo.Notes.Web.AdminApi.Host\Demo.Notes.Web.AdminApi.Host.csproj"'
7+
Start-Sleep -Seconds 2
8+
Start-Process dotnet -ArgumentList 'run --project ".\src\Web\Demo.Notes.Web\Demo.Notes.Web.UserApi.Host\Demo.Notes.Web.UserApi.Host.csproj"'
9+
Start-Sleep -Seconds 2
10+
11+
Start-Process dotnet -ArgumentList 'run --project ".\src\Web\Demo.Notes.Web\Demo.Notes.Web.Host\Demo.Notes.Web.Host.csproj"'
12+
Start-Sleep -Seconds 2
13+
Start-Process dotnet -ArgumentList 'run --project ".\src\Web\Demo.Notes.Web\Demo.Notes.Web.Blazor\Server\Demo.Notes.Web.Blazor.Server.csproj"'
14+
Start-Sleep -Seconds 2
15+

data/TestUsers.json

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
[
2+
{
3+
"SubjectId": "1",
4+
"Username": "alice",
5+
"Password": "alice",
6+
"IsActive": true,
7+
"Claims": [
8+
{
9+
"Type": "name",
10+
"Value": "Alice Smith"
11+
},
12+
{
13+
"Type": "given_name",
14+
"Value": "Alice"
15+
},
16+
{
17+
"Type": "family_name",
18+
"Value": "Smith"
19+
},
20+
{
21+
"Type": "email",
22+
"Value": "AliceSmith@email.com"
23+
},
24+
{
25+
"Type": "email_verified",
26+
"Value": "true",
27+
"ValueType": "http://www.w3.org/2001/XMLSchema#boolean"
28+
},
29+
{
30+
"Type": "website",
31+
"Value": "http://alice.com"
32+
},
33+
{
34+
"Type": "address",
35+
"Value": "{\"street_address\":\"100 Oak Ave.\",\"locality\":\"NY\",\"postal_code\":10001,\"state\":\"NY\"}",
36+
"ValueType": "json"
37+
}
38+
]
39+
},
40+
{
41+
"SubjectId": "2",
42+
"Username": "bob",
43+
"Password": "bob",
44+
"IsActive": true,
45+
"Claims": [
46+
{
47+
"Type": "name",
48+
"Value": "Bob Smith"
49+
},
50+
{
51+
"Type": "given_name",
52+
"Value": "Bob"
53+
},
54+
{
55+
"Type": "family_name",
56+
"Value": "Smith"
57+
},
58+
{
59+
"Type": "email",
60+
"Value": "BobSmith@email.com"
61+
},
62+
{
63+
"Type": "email_verified",
64+
"Value": "true",
65+
"ValueType": "http://www.w3.org/2001/XMLSchema#boolean"
66+
},
67+
{
68+
"Type": "website",
69+
"Value": "http://bob.com"
70+
},
71+
{
72+
"Type": "address",
73+
"Value": "{\"street_address\":\"123 Main St.\",\"locality\":\"San Diego\",\"postal_code\":92001,\"state\":\"CA\"}",
74+
"ValueType": "json"
75+
}
76+
]
77+
},
78+
{
79+
"SubjectId": "3",
80+
"Username": "testdeveloper",
81+
"Password": "N/A",
82+
"ProviderName": "aad",
83+
"ProviderSubjectId": "2tUrG3srdVvhMn8kV9MriSEDfxSh9YslPofytLvgm4c",
84+
"IsActive": true,
85+
"Claims": [
86+
{
87+
"Type": "name",
88+
"Value": "Test Developer"
89+
},
90+
{
91+
"Type": "given_name",
92+
"Value": "Test"
93+
},
94+
{
95+
"Type": "family_name",
96+
"Value": "Developer"
97+
},
98+
{
99+
"Type": "email",
100+
"Value": "bowencode@outlook.com"
101+
},
102+
{
103+
"Type": "email_verified",
104+
"Value": "true",
105+
"ValueType": "http://www.w3.org/2001/XMLSchema#boolean"
106+
}
107+
]
108+
},
109+
{
110+
"SubjectId": "4",
111+
"Username": "test@bowencode.com",
112+
"Password": "N/A",
113+
"ProviderName": "auth0",
114+
"ProviderSubjectId": "auth0|637b15809b9c4feffbf6e09a",
115+
"IsActive": true,
116+
"Claims": [
117+
{
118+
"Type": "name",
119+
"Value": "External Test User"
120+
},
121+
{
122+
"Type": "given_name",
123+
"Value": "Test"
124+
},
125+
{
126+
"Type": "family_name",
127+
"Value": "User"
128+
},
129+
{
130+
"Type": "email",
131+
"Value": "test@bowencode.com"
132+
}
133+
]
134+
},
135+
{
136+
"SubjectId": "5",
137+
"Username": "external@bowencode.com",
138+
"Password": "N/A",
139+
"ProviderName": "auth0",
140+
"ProviderSubjectId": "auth0|63859b949b9c4feffbf71905",
141+
"IsActive": true,
142+
"Claims": [
143+
{
144+
"Type": "name",
145+
"Value": "External User"
146+
},
147+
{
148+
"Type": "given_name",
149+
"Value": "External"
150+
},
151+
{
152+
"Type": "family_name",
153+
"Value": "User"
154+
},
155+
{
156+
"Type": "email",
157+
"Value": "external@bowencode.com"
158+
}
159+
]
160+
}
161+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Application x:Class="Demo.Notes.Client.Desktop.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:Demo.Notes.Client.Desktop"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
8+
</Application.Resources>
9+
</Application>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Data;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using System.Windows;
8+
9+
namespace Demo.Notes.Client.Desktop
10+
{
11+
/// <summary>
12+
/// Interaction logic for App.xaml
13+
/// </summary>
14+
public partial class App : Application
15+
{
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFramework>net6.0-windows</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<UseWPF>true</UseWPF>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="IdentityModel.OidcClient" Version="5.1.0" />
12+
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1418.22" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\..\Common\Demo.Notes.Common\Demo.Notes.Common.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Demo.Notes.Common.Model;
2+
using System;
3+
using System.Linq;
4+
5+
namespace Demo.Notes.Client.Desktop
6+
{
7+
public class ExtendedUserData : UserData
8+
{
9+
public int NoteCount { get; set; }
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.ComponentModel;
5+
using System.Linq;
6+
using System.Net.Http;
7+
using System.Net.Http.Json;
8+
using System.Runtime.CompilerServices;
9+
using System.Security.Claims;
10+
using System.Threading.Tasks;
11+
using Demo.Notes.Common.Model;
12+
using IdentityModel.Client;
13+
14+
namespace Demo.Notes.Client.Desktop
15+
{
16+
public class MainViewModel : INotifyPropertyChanged
17+
{
18+
private ClaimsPrincipal? _user;
19+
private string? _errorMessage;
20+
private UserTokenData? _tokens;
21+
22+
public string? ErrorMessage
23+
{
24+
get => _errorMessage;
25+
set => SetField(ref _errorMessage, value);
26+
}
27+
28+
public ClaimsPrincipal? User
29+
{
30+
get => _user;
31+
set
32+
{
33+
SetField(ref _user, value);
34+
NotifyOfPropertyChange(nameof(IsLoggedIn));
35+
}
36+
}
37+
38+
public ObservableCollection<ExtendedUserData> AllUsers { get; } = new ObservableCollection<ExtendedUserData>();
39+
40+
public bool IsLoggedIn => User != null;
41+
42+
public UserTokenData? Tokens
43+
{
44+
get => _tokens;
45+
set => SetField(ref _tokens, value);
46+
}
47+
48+
#region INotifyPropertyChanged
49+
public event PropertyChangedEventHandler? PropertyChanged;
50+
51+
protected virtual void NotifyOfPropertyChange([CallerMemberName] string? propertyName = null)
52+
{
53+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
54+
}
55+
56+
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
57+
{
58+
if (EqualityComparer<T>.Default.Equals(field, value))
59+
return false;
60+
field = value;
61+
NotifyOfPropertyChange(propertyName);
62+
return true;
63+
}
64+
#endregion
65+
66+
public async Task LoadData()
67+
{
68+
var adminClient = new HttpClient
69+
{
70+
BaseAddress = new Uri("https://localhost:7299")
71+
};
72+
var userClient = new HttpClient
73+
{
74+
BaseAddress = new Uri("https://localhost:7274")
75+
};
76+
if (Tokens?.AccessToken != null)
77+
{
78+
adminClient.SetBearerToken(Tokens.AccessToken);
79+
userClient.SetBearerToken(Tokens.AccessToken);
80+
}
81+
82+
var response = await adminClient.GetAsync("Users");
83+
if (!response.IsSuccessStatusCode)
84+
{
85+
ErrorMessage = $"{response.StatusCode}: {response.ReasonPhrase}";
86+
return;
87+
}
88+
89+
var users = await response.Content.ReadFromJsonAsync<List<ExtendedUserData>>();
90+
AllUsers.Clear();
91+
92+
var allNotes = await userClient.GetFromJsonAsync<List<NoteData>>("api/notes");
93+
94+
if (users != null)
95+
{
96+
foreach (ExtendedUserData userData in users)
97+
{
98+
userData.NoteCount = allNotes?.Count(n => n.UserId == userData.Id) ?? -1;
99+
AllUsers.Add(userData);
100+
}
101+
}
102+
}
103+
104+
public void ResetData()
105+
{
106+
ErrorMessage = null;
107+
User = null;
108+
Tokens = null;
109+
AllUsers.Clear();
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)