Skip to content

Commit bbbe763

Browse files
added authentication with demo identity server
1 parent 7f12943 commit bbbe763

File tree

8 files changed

+153
-47
lines changed

8 files changed

+153
-47
lines changed

CarvedRock.Api/CarvedRock.Api.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.3.0" />
11+
<PackageReference Include="IdentityModel" Version="6.0.0" />
12+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
1113
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
1214
</ItemGroup>
1315

CarvedRock.Api/Program.cs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
using System.Diagnostics;
1+
using System.IdentityModel.Tokens.Jwt;
22
using CarvedRock.Data;
33
using CarvedRock.Domain;
44
using Hellang.Middleware.ProblemDetails;
55
using Microsoft.Data.Sqlite;
66
using CarvedRock.Api;
7+
using Microsoft.Extensions.Options;
8+
using Microsoft.IdentityModel.Tokens;
9+
using Swashbuckle.AspNetCore.SwaggerGen;
710

811
var builder = WebApplication.CreateBuilder(args);
912
builder.Services.AddProblemDetails(opts =>
@@ -19,45 +22,55 @@
1922
opts.Rethrow<SqliteException>();
2023
opts.MapToStatusCode<Exception>(StatusCodes.Status500InternalServerError);
2124
});
22-
//builder.Logging.AddFilter("CarvedRock", LogLevel.Debug);
2325

24-
// var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
25-
// var tracePath = Path.Join(path, $"Log_CarvedRock_{DateTime.Now.ToString("yyyyMMdd-HHmm")}.txt");
26-
// Trace.Listeners.Add(new TextWriterTraceListener(System.IO.File.CreateText(tracePath)));
27-
// Trace.AutoFlush = true;
26+
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
27+
builder.Services.AddAuthentication("Bearer")
28+
.AddJwtBearer("Bearer", options =>
29+
{
30+
options.Authority = "https://demo.duendesoftware.com";
31+
options.Audience = "api";
32+
options.TokenValidationParameters = new TokenValidationParameters
33+
{
34+
NameClaimType = "email"
35+
};
36+
});
2837

29-
// Services
3038
builder.Services.AddControllers();
3139
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
3240
builder.Services.AddEndpointsApiExplorer();
41+
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerOptions>();
3342
builder.Services.AddSwaggerGen();
3443

3544
builder.Services.AddScoped<IProductLogic, ProductLogic>();
36-
3745
builder.Services.AddDbContext<LocalContext>();
3846
builder.Services.AddScoped<ICarvedRockRepository, CarvedRockRepository>();
3947

4048
var app = builder.Build();
4149

42-
app.UseMiddleware<CriticalExceptionMiddleware>();
43-
app.UseProblemDetails();
44-
4550
using (var scope = app.Services.CreateScope())
4651
{
4752
var services = scope.ServiceProvider;
4853
var context = services.GetRequiredService<LocalContext>();
4954
context.MigrateAndCreateData();
5055
}
5156

52-
// HTTP request pipeline
57+
app.UseMiddleware<CriticalExceptionMiddleware>();
58+
app.UseProblemDetails();
59+
5360
if (app.Environment.IsDevelopment())
5461
{
5562
app.UseSwagger();
56-
app.UseSwaggerUI();
63+
app.UseSwaggerUI(options =>
64+
{
65+
options.OAuthClientId("interactive.public.short");
66+
options.OAuthAppName("CarvedRock API");
67+
options.OAuthUsePkce();
68+
});
5769
}
5870
app.MapFallback(() => Results.Redirect("/swagger"));
5971
app.UseHttpsRedirection();
72+
app.UseAuthentication();
6073
app.UseAuthorization();
61-
app.MapControllers();
74+
app.MapControllers().RequireAuthorization();
6275

6376
app.Run();

CarvedRock.Api/SwaggerHelpers.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using IdentityModel.Client;
2+
using Microsoft.Extensions.Options;
3+
using Microsoft.OpenApi.Models;
4+
using Swashbuckle.AspNetCore.SwaggerGen;
5+
6+
namespace CarvedRock.Api;
7+
8+
public class SwaggerOptions : IConfigureOptions<SwaggerGenOptions>
9+
{
10+
private readonly IConfiguration _config;
11+
private readonly ILogger<SwaggerOptions> _logger;
12+
13+
public SwaggerOptions(IConfiguration config, ILogger<SwaggerOptions> logger)
14+
{
15+
_config = config;
16+
_logger = logger;
17+
}
18+
19+
public void Configure(SwaggerGenOptions options)
20+
{
21+
try
22+
{
23+
var disco = GetDiscoveryDocument();
24+
var oauthScopes = new Dictionary<string, string>
25+
{
26+
{ "api", "Resource access: api" },
27+
{ "openid", "OpenID information"},
28+
{ "profile", "User profile information" },
29+
{ "email", "User email address" }
30+
};
31+
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
32+
{
33+
Type = SecuritySchemeType.OAuth2,
34+
Flows = new OpenApiOAuthFlows
35+
{
36+
AuthorizationCode = new OpenApiOAuthFlow
37+
{
38+
AuthorizationUrl = new Uri(disco.AuthorizeEndpoint),
39+
TokenUrl = new Uri(disco.TokenEndpoint),
40+
Scopes = oauthScopes
41+
}
42+
}
43+
});
44+
options.AddSecurityRequirement(new OpenApiSecurityRequirement
45+
{
46+
{
47+
new OpenApiSecurityScheme
48+
{
49+
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
50+
},
51+
oauthScopes.Keys.ToArray()
52+
}
53+
});
54+
}
55+
catch (Exception ex)
56+
{
57+
_logger.LogWarning("Error loading discovery document for Swagger UI");
58+
}
59+
}
60+
61+
private DiscoveryDocumentResponse GetDiscoveryDocument()
62+
{
63+
var client = new HttpClient();
64+
var authority = "https://demo.duendesoftware.com";
65+
return client.GetDiscoveryDocumentAsync(authority)
66+
.GetAwaiter()
67+
.GetResult();
68+
}
69+
}

CarvedRock.WebApp/CarvedRock.WebApp.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@
1010
<Folder Include="wwwroot\images\" />
1111
</ItemGroup>
1212

13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.1" />
15+
</ItemGroup>
16+
1317
</Project>

CarvedRock.WebApp/Pages/Error.cshtml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using Microsoft.AspNetCore.Mvc;
22
using Microsoft.AspNetCore.Mvc.RazorPages;
33
using System.Diagnostics;
4+
using Microsoft.AspNetCore.Authorization;
45

56
namespace CarvedRock.WebApp.Pages
67
{
78
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
89
[IgnoreAntiforgeryToken]
10+
[AllowAnonymous]
911
public class ErrorModel : PageModel
1012
{
1113
public string? RequestId { get; set; }

CarvedRock.WebApp/Pages/Index.cshtml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using Microsoft.AspNetCore.Mvc.RazorPages;
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Mvc.RazorPages;
23

34
namespace CarvedRock.WebApp.Pages
45
{
6+
[AllowAnonymous]
57
public class IndexModel : PageModel
68
{
79
private readonly ILogger<IndexModel> _logger;

CarvedRock.WebApp/Pages/Listing.cshtml.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System.Net.Http.Headers;
12
using CarvedRock.WebApp.Models;
3+
using Microsoft.AspNetCore.Authentication;
24
using Microsoft.AspNetCore.Mvc;
35
using Microsoft.AspNetCore.Mvc.RazorPages;
46

@@ -8,12 +10,14 @@ public class ListingModel : PageModel
810
{
911
private readonly HttpClient _apiClient;
1012
private readonly ILogger<ListingModel> _logger;
13+
private readonly HttpContext? _httpCtx;
1114

12-
public ListingModel(HttpClient apiClient, ILogger<ListingModel> logger)
15+
public ListingModel(HttpClient apiClient, ILogger<ListingModel> logger, IHttpContextAccessor httpContextAccessor)
1316
{
1417
_logger = logger;
15-
_apiClient = apiClient;
18+
_apiClient = apiClient;
1619
_apiClient.BaseAddress = new Uri("https://localhost:7213/");
20+
_httpCtx = httpContextAccessor.HttpContext;
1721
}
1822

1923
public List<Product> Products { get; set; }
@@ -27,6 +31,14 @@ public async Task OnGetAsync()
2731
throw new Exception("failed");
2832
}
2933

34+
if (_httpCtx != null)
35+
{
36+
var accessToken = await _httpCtx.GetTokenAsync("access_token");
37+
_apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
38+
// for a better way to include and manage access tokens for API calls:
39+
// https://identitymodel.readthedocs.io/en/latest/aspnetcore/web.html
40+
}
41+
3042
var response = await _apiClient.GetAsync($"Product?category={cat}");
3143
if (!response.IsSuccessStatusCode)
3244
{

CarvedRock.WebApp/Program.cs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,50 @@
1-
using Microsoft.AspNetCore.HttpLogging;
1+
using System.IdentityModel.Tokens.Jwt;
2+
using Microsoft.IdentityModel.Tokens;
23

34
var builder = WebApplication.CreateBuilder(args);
4-
builder.Services.AddHttpLogging(logging =>
5+
6+
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
7+
builder.Services.AddAuthentication(options =>
58
{
6-
// https://bit.ly/aspnetcore6-httplogging
7-
logging.LoggingFields = HttpLoggingFields.All;
8-
logging.MediaTypeOptions.AddText("application/javascript");
9-
logging.RequestBodyLogLimit = 4096;
10-
logging.ResponseBodyLogLimit = 4096;
11-
});
12-
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
13-
builder.Services.AddW3CLogging(opts => {
14-
// https://bit.ly/aspnetcore6-w3clogger
15-
opts.LoggingFields = W3CLoggingFields.All;
16-
opts.FileSizeLimit = 5 * 1024 * 1024;
17-
opts.RetainedFileCountLimit = 2;
18-
opts.FileName = "CarvedRock-W3C-UI";
19-
opts.LogDirectory = Path.Combine(path, "logs");
20-
opts.FlushInterval = TimeSpan.FromSeconds(2);
9+
options.DefaultScheme = "Cookies";
10+
options.DefaultChallengeScheme = "oidc";
11+
})
12+
.AddCookie("Cookies")
13+
.AddOpenIdConnect("oidc", options =>
14+
{
15+
options.Authority = "https://demo.duendesoftware.com";
16+
options.ClientId = "interactive.confidential";
17+
options.ClientSecret = "secret";
18+
options.ResponseType = "code";
19+
options.Scope.Add("openid");
20+
options.Scope.Add("profile");
21+
options.Scope.Add("email");
22+
options.Scope.Add("api");
23+
options.Scope.Add("offline_access");
24+
options.GetClaimsFromUserInfoEndpoint = true;
25+
options.TokenValidationParameters = new TokenValidationParameters
26+
{
27+
NameClaimType = "email"
28+
};
29+
options.SaveTokens = true;
2130
});
22-
23-
// Add services to the container.
31+
builder.Services.AddHttpContextAccessor();
2432
builder.Services.AddRazorPages();
2533
builder.Services.AddHttpClient();
2634

2735
var app = builder.Build();
28-
app.UseHttpLogging();
29-
app.UseW3CLogging();
3036

31-
// Configure the HTTP request pipeline.
32-
// if (!app.Environment.IsDevelopment())
33-
// {
34-
app.UseExceptionHandler("/Error");
35-
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
36-
app.UseHsts();
37-
//}
37+
app.UseExceptionHandler("/Error");
38+
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
39+
app.UseHsts();
3840

3941
app.UseHttpsRedirection();
4042
app.UseStaticFiles();
4143

4244
app.UseRouting();
43-
45+
app.UseAuthentication();
4446
app.UseAuthorization();
4547

46-
app.MapRazorPages();
48+
app.MapRazorPages().RequireAuthorization();
4749

4850
app.Run();

0 commit comments

Comments
 (0)