Skip to content

Commit

Permalink
Merge pull request #3 from dahlsailrunner/auth
Browse files Browse the repository at this point in the history
added authentication with demo identity server
  • Loading branch information
dahlsailrunner committed Jan 14, 2022
2 parents 7f12943 + bbbe763 commit 1d420f5
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CarvedRock.Api/CarvedRock.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

<ItemGroup>
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.3.0" />
<PackageReference Include="IdentityModel" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>

Expand Down
41 changes: 27 additions & 14 deletions CarvedRock.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using CarvedRock.Data;
using CarvedRock.Domain;
using Hellang.Middleware.ProblemDetails;
using Microsoft.Data.Sqlite;
using CarvedRock.Api;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Swashbuckle.AspNetCore.SwaggerGen;

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

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

// Services
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerOptions>();
builder.Services.AddSwaggerGen();

builder.Services.AddScoped<IProductLogic, ProductLogic>();

builder.Services.AddDbContext<LocalContext>();
builder.Services.AddScoped<ICarvedRockRepository, CarvedRockRepository>();

var app = builder.Build();

app.UseMiddleware<CriticalExceptionMiddleware>();
app.UseProblemDetails();

using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<LocalContext>();
context.MigrateAndCreateData();
}

// HTTP request pipeline
app.UseMiddleware<CriticalExceptionMiddleware>();
app.UseProblemDetails();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwaggerUI(options =>
{
options.OAuthClientId("interactive.public.short");
options.OAuthAppName("CarvedRock API");
options.OAuthUsePkce();
});
}
app.MapFallback(() => Results.Redirect("/swagger"));
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapControllers().RequireAuthorization();

app.Run();
69 changes: 69 additions & 0 deletions CarvedRock.Api/SwaggerHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using IdentityModel.Client;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace CarvedRock.Api;

public class SwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IConfiguration _config;
private readonly ILogger<SwaggerOptions> _logger;

public SwaggerOptions(IConfiguration config, ILogger<SwaggerOptions> logger)
{
_config = config;
_logger = logger;
}

public void Configure(SwaggerGenOptions options)
{
try
{
var disco = GetDiscoveryDocument();
var oauthScopes = new Dictionary<string, string>
{
{ "api", "Resource access: api" },
{ "openid", "OpenID information"},
{ "profile", "User profile information" },
{ "email", "User email address" }
};
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(disco.AuthorizeEndpoint),
TokenUrl = new Uri(disco.TokenEndpoint),
Scopes = oauthScopes
}
}
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
oauthScopes.Keys.ToArray()
}
});
}
catch (Exception ex)
{
_logger.LogWarning("Error loading discovery document for Swagger UI");
}
}

private DiscoveryDocumentResponse GetDiscoveryDocument()
{
var client = new HttpClient();
var authority = "https://demo.duendesoftware.com";
return client.GetDiscoveryDocumentAsync(authority)
.GetAwaiter()
.GetResult();
}
}
4 changes: 4 additions & 0 deletions CarvedRock.WebApp/CarvedRock.WebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@
<Folder Include="wwwroot\images\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.1" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions CarvedRock.WebApp/Pages/Error.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;

namespace CarvedRock.WebApp.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
[AllowAnonymous]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
Expand Down
4 changes: 3 additions & 1 deletion CarvedRock.WebApp/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace CarvedRock.WebApp.Pages
{
[AllowAnonymous]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
Expand Down
16 changes: 14 additions & 2 deletions CarvedRock.WebApp/Pages/Listing.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net.Http.Headers;
using CarvedRock.WebApp.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

Expand All @@ -8,12 +10,14 @@ public class ListingModel : PageModel
{
private readonly HttpClient _apiClient;
private readonly ILogger<ListingModel> _logger;
private readonly HttpContext? _httpCtx;

public ListingModel(HttpClient apiClient, ILogger<ListingModel> logger)
public ListingModel(HttpClient apiClient, ILogger<ListingModel> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_apiClient = apiClient;
_apiClient = apiClient;
_apiClient.BaseAddress = new Uri("https://localhost:7213/");
_httpCtx = httpContextAccessor.HttpContext;
}

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

if (_httpCtx != null)
{
var accessToken = await _httpCtx.GetTokenAsync("access_token");
_apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// for a better way to include and manage access tokens for API calls:
// https://identitymodel.readthedocs.io/en/latest/aspnetcore/web.html
}

var response = await _apiClient.GetAsync($"Product?category={cat}");
if (!response.IsSuccessStatusCode)
{
Expand Down
62 changes: 32 additions & 30 deletions CarvedRock.WebApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
using Microsoft.AspNetCore.HttpLogging;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>

JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
builder.Services.AddAuthentication(options =>
{
// https://bit.ly/aspnetcore6-httplogging
logging.LoggingFields = HttpLoggingFields.All;
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
builder.Services.AddW3CLogging(opts => {
// https://bit.ly/aspnetcore6-w3clogger
opts.LoggingFields = W3CLoggingFields.All;
opts.FileSizeLimit = 5 * 1024 * 1024;
opts.RetainedFileCountLimit = 2;
opts.FileName = "CarvedRock-W3C-UI";
opts.LogDirectory = Path.Combine(path, "logs");
opts.FlushInterval = TimeSpan.FromSeconds(2);
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://demo.duendesoftware.com";
options.ClientId = "interactive.confidential";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("api");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "email"
};
options.SaveTokens = true;
});

// Add services to the container.
builder.Services.AddHttpContextAccessor();
builder.Services.AddRazorPages();
builder.Services.AddHttpClient();

var app = builder.Build();
app.UseHttpLogging();
app.UseW3CLogging();

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

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
app.MapRazorPages().RequireAuthorization();

app.Run();

0 comments on commit 1d420f5

Please sign in to comment.