Skip to content

Commit 186a98d

Browse files
committed
First commit
1 parent 0cf112a commit 186a98d

File tree

430 files changed

+28137
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

430 files changed

+28137
-2
lines changed

.github/workflows/push-to-main.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Integration_Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
15+
env:
16+
ASPNETCORE_ENVIRONMENT: 'Development'
17+
# Parameters__auth-base-url: ${{ secrets.AUTH_BASE_URL }}
18+
# Parameters__test-auth0-client-id: ${{ secrets.TEST_AUTH0_CLIENT_ID }}
19+
# Parameters__test-auth0-client-secret: ${{ secrets.TEST_AUTH0_CLIENT_SECRET }}
20+
# Parameters__auth0-audience: ${{ secrets.AUTH0_AUDIENCE }}
21+
# Parameters__test-auth0-admin-username: ${{ secrets.TEST_AUTH0_ADMIN_USERNAME }}
22+
# Parameters__test-auth0-admin-password: ${{ secrets.TEST_AUTH0_ADMIN_PASSWORD }}
23+
# Parameters__test-auth0-user1-username: ${{ secrets.TEST_AUTH0_USER1_USERNAME }}
24+
# Parameters__test-auth0-user1-password: ${{ secrets.TEST_AUTH0_USER1_PASSWORD }}
25+
# Parameters__test-auth0-user2-username: ${{ secrets.TEST_AUTH0_USER2_USERNAME }}
26+
# Parameters__test-auth0-user2-password: ${{ secrets.TEST_AUTH0_USER2_PASSWORD }}
27+
# Parameters__test-auth0-userinactive-username: ${{ secrets.TEST_AUTH0_USERINACTIVE_USERNAME }}
28+
# Parameters__test-auth0-userinactive-password: ${{ secrets.TEST_AUTH0_USERINACTIVE_PASSWORD }}
29+
# ConnectionStrings__messaging: ${{ secrets.MESSAGING_URL }}
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Set up .NET
36+
uses: actions/setup-dotnet@v4
37+
with:
38+
dotnet-version: '9.0.x'
39+
40+
- name: Restore dependencies
41+
run: dotnet restore
42+
43+
- name: Build
44+
run: dotnet build --no-restore --configuration Release
45+
46+
- name: Run tests
47+
if: ${{ success() }}
48+
run: dotnet test --no-build --configuration Release
49+

README.md

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,64 @@
1-
# yarp-security-api-and-ui
2-
Security layer (API) that you can use to protect your Yarp routes and the apis behind it. A Blazor ui is available to configure your thing. Subscriptions and tenants management included.
1+
# Yarp-security-api-and-ui (alpha)
2+
3+
![Yarp Security API and UI](./best-schema.png)
4+
5+
- A security API to protect your backend.
6+
- Yarp proxy as an entry point.
7+
- Compatible with any OAuth/OpenID Connect provider (e.g., Keycloak).
8+
- Optional security UI for quick bootstrapping (Blazor, and components in auto mode).
9+
- **Designed to manage subscriptions and multi-tenants.**
10+
11+
## Goal
12+
13+
This project allows you to choose any OAuth provider and remain independent in terms of your security design. You can add any frontend (Blazor, Next.js, SvelteKit) or backend APIs in front or behind this security layer. For a full SPA, you can modify Yarp to be a full BFF (Backend for Frontend).
14+
15+
## Usage
16+
17+
Consider this project as a draft template rather than a complete setup. Customize it based on your needs.
18+
19+
*Yarp, with the support of the Security API, will protect your backend with its powerful authorization handler mechanism and will forward your calls to your backends with the needed information (userid, tenantid) encoded in the headers.*
20+
21+
> **Warning:** Never use any of the secrets exposed here and never use the Keycloak realm file to protect a real project exposed on the web. Always recreate your own secrets and configurations.
22+
23+
> **Warning:** It's an alpha project, don't go on prod before strong validation on your side...
24+
25+
## Running the Project
26+
27+
1. Run the Aspire AppHost.
28+
2. Access the Blazor Security UI.
29+
30+
User accounts for login in Security UI:
31+
32+
| User | Password | Actions |
33+
|------|-------------|------|
34+
| adminuser | admin | can manage system roles and authorizations |
35+
| user1| user | manage subscription and tenant |
36+
37+
Don't remove the "TenantManger" role on the user1, or detatch him from the subscription, or your will be locked out of your tenant.
38+
(implements your own better rules).
39+
40+
3. Play and deep dive in the code...
41+
42+
*If you want to run the integration test project, pls comment this line:
43+
`await Task.Delay(120000);` in AspireFixiture.cs it's only there for github actions (waiting for Keycloak to be up).*
44+
45+
## Customization
46+
47+
You can modify the setup based on your requirements. The project is configured with Keycloak and RabbitMQ (for revoke cache requests) but is compatible with other systems like Azure Service Bus (tested) and OAuth (tested). You can also remove some parts if needed (caching, service bus)
48+
49+
## Dependencies
50+
51+
Show your support for the following dependencies on their GitHub pages:
52+
- [Blazor FluentUI](https://github.com/microsoft/fluentui-blazor)
53+
- [MassTransit](https://github.com/MassTransit/MassTransit)
54+
- [LanguageExt](https://github.com/louthy/language-ext)
55+
- ...
56+
57+
## Subscriptions and multi-tenants
58+
59+
For your backend APIs, in the EF Core pooled DbContext factory, the user is injected (see Security API), so you can easily use EF Core query filters for a simple setup or implement your own methods to inject a DbConnection string by tenant...
60+
61+
## How You Can Help
62+
63+
If you find this project useful, you can help by reviewing the implementation or contributing to make it more generic (user onboarding from oauth provider via webhook or other stuff). It's a side project on a boring and not "sexy" topic for me, but I hope it can be helpful to some of you. This project aims to avoid fully managed pricey solutions because, in the end, we only need simple OAuth authentication and to manage our authorization layer by ourselves. Help on that will be appreciated.
64+

UbikLink.AppHost/Program.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//TODO: implement for dev and prod environnement
2+
3+
var builder = DistributedApplication.CreateBuilder(args);
4+
5+
//Secret
6+
var postgresUsername = builder.AddParameter("postgres-username", secret: true);
7+
var postgresPassword = builder.AddParameter("postgres-password", secret: true);
8+
var securitytoken = builder.AddParameter("security-api-token", secret: true);
9+
var authBaseUrl = builder.AddParameter("auth-base-url", secret: true);
10+
var authMetadataUrl = builder.AddParameter("auth-metadata-url", secret: true);
11+
var authTokenUrl = builder.AddParameter("auth-token-url", secret: true);
12+
var authAudience = builder.AddParameter("auth0-audience", secret: true);
13+
var securityClientAppId = builder.AddParameter("auth-security-client-app-id", secret: true);
14+
var securityClientAppSecret = builder.AddParameter("auth-security-client-app-secret", secret: true);
15+
var messagebusConnectionString = builder.AddParameter("messaging-url", secret: true);
16+
var rabbitUser = builder.AddParameter("rabbit-username", secret: true);
17+
var rabbitPassword = builder.AddParameter("rabbit-password", secret: true);
18+
var transportType = builder.AddParameter("transport-type", secret: false);
19+
20+
//Postgres (local)
21+
var db = builder.AddPostgres("ubiklink-postgres", postgresUsername, postgresPassword)
22+
.WithDataVolume(isReadOnly: false)
23+
.WithEnvironment("POSTGRES_DB", "postgres")
24+
.WithPgAdmin()
25+
.WithLifetime(ContainerLifetime.Persistent);
26+
27+
//Azure Service Bus or rabbitmq (option)
28+
var serviceBus = builder.AddConnectionString("messaging");
29+
30+
//RabbitMQ (local)
31+
var rabbitmq = builder.AddRabbitMQ("ubiklink-rabbitmq", rabbitUser, rabbitPassword)
32+
.WithManagementPlugin()
33+
.WithLifetime(ContainerLifetime.Persistent);
34+
35+
//oAuth/openIdc (local) with keycloack
36+
var keycloak = builder.AddKeycloak("keycloak", 8080)
37+
.WithRealmImport("./Realm")
38+
.WithLifetime(ContainerLifetime.Persistent);
39+
40+
//Azure redis cache (for prod)
41+
//var cache = builder.AddAzureRedis("cache");
42+
43+
//Redis cache (local)
44+
var cache = builder.AddRedis("cache")
45+
.WithLifetime(ContainerLifetime.Persistent);
46+
47+
//Security API
48+
var securityDB = db.AddDatabase("ubiklink-security-db", "ubiklink_security_db");
49+
var securityApi = builder.AddProject<Projects.UbikLink_Security_Api>("ubiklink-security-api")
50+
.WithEnvironment("Proxy__Token", securitytoken)
51+
.WithEnvironment("ConnectionStrings__messaging", messagebusConnectionString)
52+
.WithEnvironment("Messaging__Transport", transportType)
53+
.WithEnvironment("Messaging__RabbitUser", rabbitUser)
54+
.WithEnvironment("Messaging__RabbitPassword", rabbitPassword)
55+
.WithReference(securityDB)
56+
.WaitFor(securityDB)
57+
.WithReference(rabbitmq)
58+
.WithReference(serviceBus);
59+
60+
//Proxy
61+
var proxy = builder.AddProject<Projects.UbikLink_Proxy>("ubiklink-proxy")
62+
.WithEnvironment("Proxy__Token",securitytoken)
63+
.WithEnvironment("Parameters__auth-base-url", authBaseUrl)
64+
.WithEnvironment("Parameters__auth0-audience", authAudience)
65+
.WithEnvironment("ConnectionStrings__messaging", messagebusConnectionString)
66+
.WithEnvironment("Messaging__Transport", transportType)
67+
.WithEnvironment("Messaging__RabbitUser", rabbitUser)
68+
.WithEnvironment("Messaging__RabbitPassword", rabbitPassword)
69+
.WithReference(securityApi)
70+
.WithReference(keycloak)
71+
.WithReference(cache)
72+
.WithReference(serviceBus)
73+
.WithReference(rabbitmq)
74+
.WaitFor(cache)
75+
.WaitFor(securityApi)
76+
.WaitFor(keycloak);
77+
78+
//.WithReference(rabbitmq)
79+
// .WaitFor(rabbitmq)
80+
81+
//Security UI
82+
builder.AddProject<Projects.UbikLink_Security_UI>("ubiklink-security-ui")
83+
.WithReference(cache)
84+
.WaitFor(cache)
85+
.WithReference(proxy)
86+
.WaitFor(proxy)
87+
.WithReference(serviceBus)
88+
.WithReference(rabbitmq)
89+
.WithReference(keycloak)
90+
.WaitFor(keycloak)
91+
.WithEnvironment("ConnectionStrings__messaging", messagebusConnectionString)
92+
.WithEnvironment("AuthConfig__MetadataAddress", authMetadataUrl)
93+
.WithEnvironment("AuthConfig__Authority", authBaseUrl)
94+
.WithEnvironment("AuthConfig__Audience", authAudience)
95+
.WithEnvironment("AuthConfig__TokenUrl", authTokenUrl)
96+
.WithEnvironment("AuthConfig__ClientId", securityClientAppId)
97+
.WithEnvironment("AuthConfig__ClientSecret", securityClientAppSecret)
98+
.WithEnvironment("Messaging__Transport", transportType)
99+
.WithEnvironment("Messaging__RabbitUser", rabbitUser)
100+
.WithEnvironment("Messaging__RabbitPassword", rabbitPassword);
101+
102+
await builder.Build().RunAsync();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"profiles": {
3+
"https": {
4+
"commandName": "Project",
5+
"launchBrowser": false,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development",
8+
"DOTNET_ENVIRONMENT": "Development",
9+
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21123",
10+
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22164"
11+
},
12+
"dotnetRunMessages": true,
13+
"applicationUrl": "https://localhost:17150;http://localhost:15030"
14+
},
15+
"http": {
16+
"commandName": "Project",
17+
"environmentVariables": {
18+
"ASPNETCORE_ENVIRONMENT": "Development",
19+
"DOTNET_ENVIRONMENT": "Development",
20+
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19169",
21+
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20212"
22+
},
23+
"dotnetRunMessages": true,
24+
"applicationUrl": "http://localhost:15030"
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)