Web Api found ...

This commit is contained in:
Paul Schneider
2025-02-11 04:45:05 +00:00
parent d1cadd9df8
commit 6cd5f1d041
23 changed files with 215 additions and 870 deletions

10
src/Api/Api.csproj Normal file
View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.12" />
</ItemGroup>
</Project>

77
src/Api/Program.cs Normal file
View File

@ -0,0 +1,77 @@
/*
Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/
Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
Source code and license this software can be found
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
using Microsoft.AspNetCore.Mvc;
internal class Program
{
private static async Task Main(string[] args)
{
Console.Title = "API";
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
// accepts any access token issued by identity server
// adds an authorization policy for scope 'api1'
services
.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy
.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
})
.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
})
.AddControllers();
// accepts any access token issued by identity server
var authenticationBuilder = services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
using (var app = builder.Build())
{
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors("default");
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
).RequireAuthorization("ApiScope");
await app.RunAsync();
}
}
}

10
src/Api/appsettings.json Normal file
View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -77,10 +77,12 @@ public static class Config
PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.OfflineAccess,
"scope2" }
},
};

View File

@ -2,6 +2,7 @@ using System.Globalization;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Util.Store;
using IdentityServer8;
using IdentityServer8.Hosting;
using IdentityServer8.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
@ -187,8 +188,9 @@ internal static class HostingExtensions
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>()
.AddJwtBearerClientAuthentication()
;
services.AddScoped<IProfileService, ProfileService>();
//services.AddScoped<IProfileService, ProfileService>();
if (builder.Environment.IsDevelopment())
{
@ -207,7 +209,15 @@ internal static class HostingExtensions
// TODO .AddServerSideSessionStore<YavscServerSideSessionStore>()
var authenticationBuilder = services.AddAuthentication();
var authenticationBuilder = services.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails=true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
authenticationBuilder.AddGoogle(options =>
{
@ -251,13 +261,7 @@ internal static class HostingExtensions
};
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
_ = builder.WithOrigins("*");
});
});
// Add the system clock service
@ -313,10 +317,10 @@ internal static class HostingExtensions
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
{
policy.RequireAuthenticatedUser()
.RequireClaim("scope", "scope2");
});
options.AddPolicy("Performer", policy =>
{
policy
@ -334,11 +338,29 @@ internal static class HostingExtensions
// options.AddPolicy("BuildingEntry", policy => policy.Requirements.Add(new OfficeEntryRequirement()));
options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());
options.AddPolicy("IsTheAuthor", policy => policy.Requirements.Add(new EditPermission()));
})
.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
_ = builder.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyMethod();
});
options.AddPolicy("default", policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
// accepts any access token issued by identity server
return builder.Build();
}
public static WebApplication ConfigurePipeline(this WebApplication app)
@ -357,9 +379,6 @@ internal static class HostingExtensions
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.MapGet("/api/me", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
).RequireAuthorization("ApiScope");
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

View File

@ -8,11 +8,11 @@ using Yavsc.Models;
namespace Yavsc.Services
{
public class ProfileService : IProfileService
public class ProfileService : DefaultProfileService, IProfileService
{
private readonly UserManager<ApplicationUser> _userManager;
public ProfileService(
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager, ILogger<DefaultProfileService> logger) : base(logger)
{
_userManager = userManager;
}
@ -21,50 +21,56 @@ namespace Yavsc.Services
ProfileDataRequestContext context,
ApplicationUser user)
{
var requestedApiResources = context.RequestedResources.Resources.ApiResources.Select(
r => r.Name
).ToArray();
var requestedApiScopes = context.RequestedResources.Resources.ApiScopes.Select(
s => s.Name
).ToArray();
var allowedScopes = context.Client.AllowedScopes
.Where(s => s != JwtClaimTypes.Subject)
var requestedScopes = context.Client.AllowedScopes
.Where(s => s != JwtClaimTypes.Subject
&& requestedApiScopes.Contains(s))
.ToList();
if (allowedScopes.Contains("profile"))
if (context.RequestedClaimTypes.Contains("profile"))
if (requestedScopes.Contains("profile"))
{
allowedScopes.Remove("profile");
allowedScopes.Add(JwtClaimTypes.Name);
allowedScopes.Add(JwtClaimTypes.FamilyName);
allowedScopes.Add(JwtClaimTypes.Email);
allowedScopes.Add(JwtClaimTypes.PreferredUserName);
allowedScopes.Add("http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
requestedScopes.Remove("profile");
requestedScopes.Add(JwtClaimTypes.Name);
requestedScopes.Add(JwtClaimTypes.FamilyName);
requestedScopes.Add(JwtClaimTypes.Email);
requestedScopes.Add(JwtClaimTypes.PreferredUserName);
requestedScopes.Add(JwtClaimTypes.Role);
}
var claims = new List<Claim> {
new Claim(JwtClaimTypes.Subject,user.Id.ToString()),
};
foreach (var subClaim in context.Subject.Claims)
if (requestedScopes.Contains(JwtClaimTypes.Name)||
requestedScopes.Contains(JwtClaimTypes.FamilyName))
{
if (allowedScopes.Contains(subClaim.Type))
claims.Add(subClaim);
claims.Add(new Claim(JwtClaimTypes.Name, user.FullName));
}
AddClaims(allowedScopes, claims, JwtClaimTypes.Email, user.Email);
AddClaims(allowedScopes, claims, JwtClaimTypes.PreferredUserName, user.FullName);
foreach (var scope in context.Client.AllowedScopes)
if (requestedScopes.Contains(JwtClaimTypes.PreferredUserName) )
{
claims.Add(new Claim("scope", scope));
claims.Add(new Claim(JwtClaimTypes.Name, user.UserName));
}
if (requestedScopes.Contains(JwtClaimTypes.Email))
claims.Add(new Claim(JwtClaimTypes.Email, user.Email));
if (requestedScopes.Contains(JwtClaimTypes.Role))
{
var roles = await this._userManager.GetRolesAsync(user);
if (roles.Count()>0)
{
claims.Add(new Claim(JwtClaimTypes.Role,String.Join(" ",roles)));
}
}
return claims;
}
private static void AddClaims(List<string> allowedScopes, List<Claim> claims,
string claimType, string claimValue
)
{
if (allowedScopes.Contains(claimType))
if (!claims.Any(c => c.Type == claimType))
claims.Add(new Claim(claimType, claimValue));
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
@ -72,7 +78,6 @@ namespace Yavsc.Services
context.IssuedClaims = await GetClaimsFromUserAsync(context, user);
}
public async Task IsActiveAsync(IsActiveContext context)
{
var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;

View File

@ -54,14 +54,31 @@ namespace testOauthClient.Controllers
public async Task<IActionResult> GetUserInfo(CancellationToken cancellationToken)
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false });
client.DefaultRequestHeaders.Add("Accept", "application/json");
var content = await client.GetStringAsync("https://localhost:5001/api/account/me");
var obj = JsonSerializer.Deserialize<JsonElement>(content);
client.SetBearerToken(accessToken);
var content = await client.GetAsync("https://localhost:5001/api/account/me");
content.EnsureSuccessStatusCode();
var json = await content.Content.ReadAsStreamAsync();
var obj = JsonSerializer.Deserialize<JsonElement>(json);
return View("UserInfo", obj.ToString());
}
[HttpPost]
public async Task<IActionResult> GetApiCall(CancellationToken cancellationToken)
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient(new HttpClientHandler(){ AllowAutoRedirect=false });
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.SetBearerToken(accessToken);
var content = await client.GetAsync("https://localhost:6001/identity");
content.EnsureSuccessStatusCode();
var json = await content.Content.ReadAsStreamAsync();
var obj = JsonSerializer.Deserialize<JsonElement>(json);
return View("UserInfo", obj.ToString());
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";

View File

@ -4,8 +4,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:16967",
"sslPort": 44387
"applicationUrl": "http://localhost:5002",
"sslPort": 5003
}
},
"profiles": {
@ -13,7 +13,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5164",
"applicationUrl": "http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -22,7 +22,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7055;http://localhost:5164",
"applicationUrl": "https://localhost:5003;http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -23,7 +23,6 @@ public class Startup
.AddOpenIdConnect("Yavsc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.ResponseType = "code";
@ -32,6 +31,8 @@ public class Startup
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.Scope.Add("scope2");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
@ -44,6 +45,7 @@ public class Startup
NameClaimType = "name",
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
};
});
}

View File

@ -21,6 +21,11 @@
<form action="~/Home/GetUserInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Get user info</button>
</form>
<form action="~/Home/GetApiCall" method="post">
<button class="btn btn-lg btn-warning" type="submit">Api Call</button>
</form>
<form action="~/Home/PostDeviceInfo" method="post">
<button class="btn btn-lg btn-warning" type="submit">Post device info</button>
</form>

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.12" />
</ItemGroup>
<ItemGroup>
@ -10,6 +11,7 @@
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>