Skip to content

Commit

Permalink
use microsoft identity
Browse files Browse the repository at this point in the history
  • Loading branch information
Barsonax committed Jun 3, 2024
1 parent 6f3d6d0 commit f3d29bf
Show file tree
Hide file tree
Showing 11 changed files with 58 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task CreateEmployee_IsAdded()
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).CreateEmployee(createEmployeeRequest);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest);

//Assert
await response.AssertStatusCode(HttpStatusCode.Created);
Expand Down Expand Up @@ -64,7 +64,7 @@ public async Task CreateEmployee_InvalidRequest_ReturnsBadRequest(TestScenario<(
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).CreateEmployee(createEmployeeRequest);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).CreateEmployee(createEmployeeRequest);

//Assert
await response.AssertBadRequest(scenario.Input.expectedErrors);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public async Task DeleteEmployeeById_IsDeleted()
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).DeleteEmployeeById(employee.Id);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).DeleteEmployeeById(employee.Id);

//Assert
await response.AssertStatusCode(HttpStatusCode.NoContent);
Expand All @@ -29,7 +29,7 @@ public async Task DeleteEmployeeById_DoesNotExist_ReturnsNotFound()
var id = Guid.NewGuid();

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).DeleteEmployeeById(id);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).DeleteEmployeeById(id);

//Assert
await response.AssertStatusCode(HttpStatusCode.NotFound);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public async Task GetEmployeeById_ReturnsExpectedEmployee()
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployeeById(employee.Id);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadRole).GetEmployeeById(employee.Id);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
Expand All @@ -29,7 +29,7 @@ public async Task GetEmployeeById_DoesNotExist_ReturnsNotFound()
var employee = new EmployeeFaker().Generate();

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployeeById(employee.Id);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadRole).GetEmployeeById(employee.Id);

//Assert
await response.AssertStatusCode(HttpStatusCode.NotFound);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class GetEmployees : TestBase
public async Task? GetEmployees_NoEmployees_ReturnsEmptyPage()
{
//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(1, 10);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadRole).GetEmployees(1, 10);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
Expand Down Expand Up @@ -37,7 +37,7 @@ public async Task GetEmployees_FirstPage_ReturnsExpectedEmployees()
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(1, 10);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadRole).GetEmployees(1, 10);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
Expand Down Expand Up @@ -70,7 +70,7 @@ public async Task GetEmployees_SecondPage_ReturnsExpectedEmployees()
});

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadEmployeesRole).GetEmployees(2, 10);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.ReadRole).GetEmployees(2, 10);

//Assert
await response.AssertStatusCode(HttpStatusCode.OK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,10 @@ public async Task UpdateEmployeeById_IsUpdated()
var employee = new EmployeeFaker().Generate();
Sut.SeedData(context => { context.Employees.Add(employee); });

UpdateEmployeeRequest updateEmployeeRequest = new()
{
FirstName = "Updated"
};
UpdateEmployeeRequest updateEmployeeRequest = new() { FirstName = "Updated" };

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest);

//Assert
await response.AssertStatusCode(HttpStatusCode.NoContent);
Expand All @@ -41,13 +38,10 @@ public async Task UpdateEmployeeById_DoesNotExist_ReturnsNotFound()
//Arrange
var employee = new EmployeeFaker().Generate();

UpdateEmployeeRequest updateEmployeeRequest = new()
{
FirstName = "Updated"
};
UpdateEmployeeRequest updateEmployeeRequest = new() { FirstName = "Updated" };

//Act
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteEmployeesRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest);
var response = await Sut.CreateClientFor<IEmployeeApiClient>(ClaimConstants.WriteRole).UpdateEmployeeById(employee.Id, updateEmployeeRequest);

//Assert
await response.AssertStatusCode(HttpStatusCode.NotFound);
Expand Down
4 changes: 2 additions & 2 deletions CleanAspCore.Api.Tests/TestSetup/ClaimConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace CleanAspCore.Api.Tests.TestSetup;

public static class ClaimConstants
{
public static readonly Claim ReadEmployeesRole = new(ClaimTypes.Role, "reademployees");
public static readonly Claim WriteEmployeesRole = new(ClaimTypes.Role, "writeemployees");
public static readonly Claim ReadRole = new(ClaimTypes.Role, "read");
public static readonly Claim WriteRole = new(ClaimTypes.Role, "write");
}
41 changes: 32 additions & 9 deletions CleanAspCore/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using MicroElements.Swashbuckle.FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;

namespace CleanAspCore;
Expand All @@ -31,38 +32,54 @@ internal static void AddAuthServices(this WebApplicationBuilder builder)
.RequireAuthenticatedUser()
.Build());

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, x =>
{
x.TokenValidationParameters.ClockSkew = TimeSpan.FromSeconds(5);
});
var authBuilder = builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
if (builder.Environment.IsDevelopment())
{
authBuilder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme);
}
else
{
authBuilder.AddMicrosoftIdentityWebApi(builder.Configuration);
}
}

internal static void AddOpenApiServices(this WebApplicationBuilder builder)
{
if (builder.Configuration.GetValue<bool?>("DisableOpenApi") == true)
return;

var config = builder.Configuration.GetRequiredSection(Constants.AzureAd).Get<MicrosoftIdentityOptions>()!;
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SupportNonNullableReferenceTypes();
var xmlDocPath = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
options.IncludeXmlComments(xmlDocPath);
var jwtSecurityScheme = new OpenApiSecurityScheme
{
BearerFormat = "JWT",
Name = "JWT Authentication",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Type = builder.Environment.IsDevelopment() ? SecuritySchemeType.Http : SecuritySchemeType.OAuth2,
Scheme = JwtBearerDefaults.AuthenticationScheme,
Description = "Put **_ONLY_** your JWT Bearer token on textbox below!",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
},
Flows = new OpenApiOAuthFlows()
{
AuthorizationCode = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{config.TenantId}/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"https://login.microsoftonline.com/{config.TenantId}/oauth2/v2.0/token"),
Scopes = new Dictionary<string, string>
{
{ $"api://{config.ClientId}/default", "read" },
},
}
}
};
Expand All @@ -77,9 +94,15 @@ internal static void UseOpenApi(this WebApplication app)
{
if (app.Configuration.GetValue<bool?>("DisableOpenApi") == true)
return;

var config = app.Configuration.GetRequiredSection(Constants.AzureAd).Get<MicrosoftIdentityOptions>()!;
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwaggerUI(setup =>
{
setup.ConfigObject.AdditionalItems.Add("persistAuthorization", "true");
setup.OAuthClientId(config.ClientId);
setup.OAuthUsePkce();
setup.OAuthScopes($"api://{config.ClientId}/default");
});
}

internal static void RunMigrations(this WebApplication app)
Expand Down
1 change: 1 addition & 0 deletions CleanAspCore/CleanAspCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.4"/>
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.4"/>
<PackageReference Include="Microsoft.Identity.Web" Version="2.19.0"/>
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1"/>
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1"/>
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1"/>
Expand Down
4 changes: 2 additions & 2 deletions CleanAspCore/Endpoints/Employees/EmployeeEndpointConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ internal static class EmployeeEndpointConfig
internal static void AddEmployeeServices(this WebApplicationBuilder builder)
{
builder.Services.AddAuthorizationBuilder()
.AddPolicy(ReadEmployeesPolicy, policy => policy.RequireRole("reademployees"))
.AddPolicy(WriteEmployeesPolicy, policy => policy.RequireRole("writeemployees"));
.AddPolicy(ReadEmployeesPolicy, policy => policy.RequireRole("read"))
.AddPolicy(WriteEmployeesPolicy, policy => policy.RequireRole("write"));
}

internal static void AddEmployeesRoutes(this IEndpointRouteBuilder host)
Expand Down
5 changes: 5 additions & 0 deletions CleanAspCore/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
"Microsoft.AspNetCore": "Information"
}
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "88d823a1-6334-422c-8b78-1665d9b4cbac",
"ClientId": "1a338460-39f1-42e4-9b68-6988d33741cd"
},
"AllowedHosts": "*"
}
5 changes: 3 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# A productive ASP .NET minimal api template

This is a template repository showing how one can implement a clean api with ASP.NET using minimal apis. The focus on 'features' in this template is on dev productivity, the actual features of the api itself has been kept basic on purpose. Feel free to copy this repository or reuse parts of it, don't forget to give a star if you do.
This is a template repository showing how one can implement a clean api with ASP.NET using minimal apis. The focus on 'features' in this template is on dev productivity, the actual features
of the api itself has been kept basic on purpose. Feel free to copy this repository or reuse parts of it, don't forget to give a star if you do.

Some features in this template:

Expand All @@ -26,7 +27,7 @@ dotnet test
1. First generate a jwt that you can use for local testing:

```cmd
dotnet user-jwts create --role "reademployees" --role "writeemployees"
dotnet user-jwts create --role "read" --role "write"
```

NOTE: The jobs and department endpoints only require authentication but the employee endpoints require that you have the correct claims in the jwt token.
Expand Down

0 comments on commit f3d29bf

Please sign in to comment.