Skip to content

Commit e1ced9a

Browse files
committed
ADDED: ADB2C.csproj updated with additional package references and properties.
ADDED: ADB2C.http file added, containing a GET request to the ADB2C host address. ADDED: ADB2C.sln file added project and solutionDED: MeController.cs file added, containing two controller methods for GET and configuration and middleware launch profiles for added, containing appsettings file added, containing configuration settings for Azure AD B2C, OpenID and allowed hosts.
1 parent 5af4771 commit e1ced9a

8 files changed

+395
-1
lines changed

ADB2C.csproj

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
23
<PropertyGroup>
3-
<Title>Active Directory B2C Identity Test</Title>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<InvariantGlobalization>true</InvariantGlobalization>
8+
<UserSecretsId>3c5831a3-10fe-4af0-8251-3b31ae14f177</UserSecretsId>
49
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
13+
<PackageReference Include="Microsoft.AspNetCore.Session" />
14+
<PackageReference Include="Microsoft.Identity.Web" />
15+
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" />
16+
<PackageReference Include="Microsoft.Identity.Web.UI" />
17+
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
18+
<PackageReference Include="Newtonsoft.Json" />
19+
<PackageReference Include="Swashbuckle.AspNetCore" />
20+
</ItemGroup>
21+
522
</Project>

ADB2C.http

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ADB2C_HostAddress = http://localhost:5288
2+
3+
GET {{ADB2C_HostAddress}}/weatherforecast/
4+
Accept: application/json
5+
6+
###

ADB2C.sln

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Microsoft Visual Studio Solution File, Format Version 12.00
2+
#
3+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B283EBC2-E01F-412D-9339-FD56EF114549}"
4+
ProjectSection(SolutionItems) = preProject
5+
..\Directory.Build.props = ..\Directory.Build.props
6+
..\Directory.Build.targets = ..\Directory.Build.targets
7+
..\..\..\global.json = ..\..\..\global.json
8+
..\..\..\Packages\Versions.Local.props = ..\..\..\Packages\Versions.Local.props
9+
EndProjectSection
10+
EndProject
11+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ADB2C", "ADB2C.csproj", "{1F05D736-EA65-4E82-9F14-EFDB324B2F63}"
12+
EndProject
13+
Global
14+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
15+
Local|Any CPU = Local|Any CPU
16+
Debug|Any CPU = Debug|Any CPU
17+
Testing|Any CPU = Testing|Any CPU
18+
Staging|Any CPU = Staging|Any CPU
19+
Production|Any CPU = Production|Any CPU
20+
Release|Any CPU = Release|Any CPU
21+
EndGlobalSection
22+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
23+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Local|Any CPU.ActiveCfg = Local|Any CPU
24+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Local|Any CPU.Build.0 = Local|Any CPU
25+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Debug|Any CPU.Build.0 = Debug|Any CPU
27+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Testing|Any CPU.ActiveCfg = Testing|Any CPU
28+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Testing|Any CPU.Build.0 = Testing|Any CPU
29+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Staging|Any CPU.ActiveCfg = Staging|Any CPU
30+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Staging|Any CPU.Build.0 = Staging|Any CPU
31+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Production|Any CPU.ActiveCfg = Local|Any CPU
32+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Production|Any CPU.Build.0 = Local|Any CPU
33+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{1F05D736-EA65-4E82-9F14-EFDB324B2F63}.Release|Any CPU.Build.0 = Release|Any CPU
35+
EndGlobalSection
36+
GlobalSection(SolutionProperties) = preSolution
37+
HideSolutionNode = FALSE
38+
EndGlobalSection
39+
GlobalSection(ExtensibilityGlobals) = postSolution
40+
SolutionGuid = {E62FAA4C-9868-487A-AA4E-8DF41E52132B}
41+
EndGlobalSection
42+
EndGlobal

Controllers/MeController.cs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
namespace ADB2C.Controllers;
2+
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Identity.Web.Resource;
6+
using Microsoft.Identity.Web;
7+
using Microsoft.Graph;
8+
9+
[Authorize(Policy = "alloweduser")]
10+
[ApiController]
11+
public class MeController(GraphServiceClient graphClient) : ControllerBase
12+
{
13+
private readonly GraphServiceClient _graphClient = graphClient;
14+
15+
[HttpGet]
16+
[Route("api/me")]
17+
[AuthorizeForScopes(ScopeKeySection = "AzureAdB2C:Scopes")]
18+
public async Task<User> Get()
19+
{
20+
var graphUser = await _graphClient.Me
21+
.Request()
22+
// .Select(
23+
// u =>
24+
// new
25+
// {
26+
// u.AboutMe,
27+
// u.AgeGroup,
28+
// u.AdditionalData,
29+
// u.Activities,
30+
// u.City,
31+
// u.Interests,
32+
// u.Identities
33+
// }
34+
// )
35+
.GetAsync();
36+
return graphUser;
37+
}
38+
39+
[HttpPut]
40+
[Route("api/me")]
41+
[AuthorizeForScopes(ScopeKeySection = "AzureAdB2C:Scopes")]
42+
public async Task<User> Set([FromBody] System.Security.Claims.Claim claim)
43+
{
44+
var graphUser = await _graphClient.Me
45+
.Request()
46+
.UpdateAsync(
47+
new User
48+
{
49+
AdditionalData = new Dictionary<string, object> { { claim.Type, claim.Value } }
50+
}
51+
);
52+
return graphUser;
53+
}
54+
}

Program.cs

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
2+
using Microsoft.AspNetCore.HttpOverrides;
3+
using Microsoft.Identity.Web;
4+
using Microsoft.Identity.Web.UI;
5+
using Microsoft.AspNetCore.Authentication.Cookies;
6+
using Azure.Extensions.AspNetCore.Configuration.Secrets;
7+
using Azure.Identity;
8+
using Microsoft.AspNetCore.Authentication.JwtBearer;
9+
using System.IdentityModel.Tokens.Jwt;
10+
using Microsoft.IdentityModel.Logging;
11+
using Constants = Microsoft.Identity.Web.Constants;
12+
13+
var builder = WebApplication.CreateBuilder(args);
14+
15+
var initialScopes = builder.Configuration[
16+
$"{nameof(MicrosoftGraphOptions)}:{nameof(MicrosoftGraphOptions.Scopes)}"
17+
]?.Split(' ');
18+
19+
builder.Services.AddEndpointsApiExplorer();
20+
builder.Services.AddOptions();
21+
22+
// Add services to the container.
23+
builder.Services
24+
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
25+
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(Constants.AzureAdB2C))
26+
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
27+
.AddMicrosoftGraph(builder.Configuration.GetSection(nameof(MicrosoftGraphOptions)))
28+
.AddDistributedTokenCaches(); //we might need to change this to scale the app
29+
30+
// builder.Services
31+
// .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
32+
// .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection(Constants.AzureAdB2C))
33+
// .EnableTokenAcquisitionToCallDownstreamApi()
34+
// .AddMicrosoftGraph(builder.Configuration.GetSection(nameof(MicrosoftGraphOptions)))
35+
// .AddInMemoryTokenCaches();
36+
37+
builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "ADB2C", Version = "v1" }));
38+
39+
// .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
40+
// .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection(Constants.AzureAd))
41+
// .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
42+
// .AddMicrosoftGraph(builder.Configuration.GetSection(nameof(MicrosoftGraphOptions)))
43+
// .AddDistributedTokenCaches(); //we might need to change this to scale the app
44+
45+
builder.Services.AddMicrosoftGraph();
46+
builder.Services.AddProblemDetails();
47+
builder.Services.AddHealthChecks();
48+
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
49+
IdentityModelEventSource.LogCompleteSecurityArtifact = true;
50+
IdentityModelEventSource.ShowPII = true;
51+
52+
builder.Services.Configure<CookieAuthenticationOptions>(
53+
CookieAuthenticationDefaults.AuthenticationScheme,
54+
options => options.AccessDeniedPath = "/AccessDenied"
55+
);
56+
57+
// builder.Services.Configure<JwtBearerOptions>(
58+
// JwtBearerDefaults.AuthenticationScheme,
59+
// options =>
60+
// {
61+
// options.Events = new JwtBearerEvents { OnAuthenticationFailed = AuthenticationFailed };
62+
// }
63+
// );
64+
65+
builder.Services.AddAuthorization(options =>
66+
{
67+
// By default, all incoming requests will be authorized according to the default policy.
68+
options.FallbackPolicy = options.DefaultPolicy;
69+
});
70+
71+
//if the access to the webapp needs to be limited to a specific role, set the role in the appsettings.json
72+
//if the role is not set, the webapp will be open to all authenticated users
73+
//this allows you to show a friendly access denied message with optional instructions for your users
74+
//how to get access if they want to or if they can
75+
//this access policy is set on the index.html and on the controller through [Authorize(Policy = "alloweduser")] attribute
76+
var requiredUserRoleForAccess = builder.Configuration["AzureAdB2C:AllowedUsersRole"];
77+
if (!IsNullOrEmpty(requiredUserRoleForAccess))
78+
{
79+
builder.Services
80+
.AddAuthorizationBuilder()
81+
.AddDefaultPolicy(
82+
"alloweduser",
83+
policy =>
84+
{
85+
policy.RequireAuthenticatedUser();
86+
policy.RequireRole(requiredUserRoleForAccess);
87+
}
88+
);
89+
}
90+
else
91+
{
92+
builder.Services
93+
.AddAuthorizationBuilder()
94+
.AddDefaultPolicy("alloweduser", policy => policy.RequireAuthenticatedUser());
95+
}
96+
97+
builder.Services.Configure<SessionOptions>(
98+
builder.Configuration.GetSection(nameof(SessionOptions))
99+
);
100+
builder.Services.AddSession();
101+
102+
// options =>
103+
// {
104+
// options.IdleTimeout = TimeSpan.FromMinutes(1); //You can set Time
105+
// options.Cookie.IsEssential = true;
106+
// options.Cookie.SameSite = SameSiteMode.None;
107+
// options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
108+
// options.Cookie.HttpOnly = true;
109+
// });
110+
111+
builder.Services.AddRazorPages().AddMicrosoftIdentityUI();
112+
113+
builder.Services.AddHttpClient(); // use iHttpFactory as best practice, should be easy to use extra retry and hold off policies in the future
114+
115+
// The following lines code instruct the asp.net core middleware to use the data in the "roles" claim in the Authorize attribute and User.IsInrole()
116+
// See https://docs.microsoft.com/aspnet/core/security/authorization/roles?view=aspnetcore-2.2 for more info.
117+
builder.Services.Configure<OpenIdConnectOptions>(
118+
OpenIdConnectDefaults.AuthenticationScheme,
119+
builder.Configuration.GetSection(nameof(OpenIdConnectOptions))
120+
);
121+
122+
var app = builder.Build();
123+
124+
// this setting is used when you use tools like ngrok or reverse proxies like nginx which connect to http://localhost
125+
// if you don't set this setting the sign-in redirect will be http instead of https
126+
app.UseForwardedHeaders(
127+
new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto }
128+
);
129+
130+
// Configure the HTTP request pipeline.
131+
if (app.Environment.IsDevelopment())
132+
{
133+
app.UseDeveloperExceptionPage();
134+
app.UseSwagger();
135+
app.UseSwaggerUI();
136+
}
137+
else
138+
{
139+
app.UseHsts();
140+
app.UseExceptionHandler("/Error");
141+
}
142+
143+
app.UseSession();
144+
app.UseHttpsRedirection();
145+
app.UseStaticFiles();
146+
147+
app.UseRouting();
148+
149+
app.UseAuthentication();
150+
app.UseAuthorization();
151+
152+
app.UseCookiePolicy(new CookiePolicyOptions { Secure = CookieSecurePolicy.Always });
153+
154+
app.MapRazorPages();
155+
app.MapControllers();
156+
157+
// generate an api-key on startup that we can use to validate callbacks
158+
env.SetEnvironmentVariable("API-KEY", guid.NewGuid().ToString());
159+
160+
app.Run();

Properties/launchSettings.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:35859",
8+
"sslPort": 44365
9+
}
10+
},
11+
"profiles": {
12+
// "http": {
13+
// "commandName": "Project",
14+
// "dotnetRunMessages": true,
15+
// "launchBrowser": true,
16+
// "launchUrl": "swagger",
17+
// "applicationUrl": "https://localhost:5288",
18+
// "environmentVariables": {
19+
// "ASPNETCORE_ENVIRONMENT": "Development"
20+
// }
21+
// },
22+
"https": {
23+
"commandName": "Project",
24+
"dotnetRunMessages": true,
25+
"launchBrowser": true,
26+
"launchUrl": "swagger",
27+
"applicationUrl": "https://localhost:7157;http://localhost:5288",
28+
"environmentVariables": {
29+
"ASPNETCORE_ENVIRONMENT": "Development"
30+
}
31+
},
32+
"IIS Express": {
33+
"commandName": "IISExpress",
34+
"launchBrowser": true,
35+
"launchUrl": "swagger",
36+
"environmentVariables": {
37+
"ASPNETCORE_ENVIRONMENT": "Development"
38+
}
39+
}
40+
}
41+
}

appsettings.Development.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)