diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 75428c0..e886905 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -4,12 +4,17 @@ on:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
+ workflow_dispatch:
jobs:
spa-test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ - name: Install certutil (for https support)
+ run: |
+ sudo apt update
+ sudo apt install libnss3-tools -y
- uses: actions/setup-node@v4
with:
node-version: lts/*
diff --git a/MockOidcApp.Api/MockOidcApp.Api.csproj b/MockOidcApp.Api/MockOidcApp.Api.csproj
index 87736d1..dc59334 100644
--- a/MockOidcApp.Api/MockOidcApp.Api.csproj
+++ b/MockOidcApp.Api/MockOidcApp.Api.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/MockOidcApp.AppHost/Program.cs b/MockOidcApp.AppHost/Program.cs
index 5f2d205..477084b 100644
--- a/MockOidcApp.AppHost/Program.cs
+++ b/MockOidcApp.AppHost/Program.cs
@@ -1,3 +1,5 @@
+using System.Text.Json;
+
var builder = DistributedApplication.CreateBuilder(args);
var api = builder.AddProject("api")
@@ -14,6 +16,90 @@
var viteNpmInstall = builder.AddExecutable("vite-npm-install", "npm", "../MockOidcApp.Vite", "install");
vite.WaitForCompletion(viteNpmInstall);
+
+ var clientId = Guid.NewGuid().ToString();
+ var tenantId = Guid.NewGuid().ToString();
+ var certPassword = Guid.NewGuid().ToString();
+ var certExportExe = builder.AddExecutable("cert-export-exe", "dotnet", ".", "dev-certs", "https", "-ep", "./dev-certificates/aspnetapp.pfx", "-p", certPassword, "--trust", "--verbose");
+
+ var mockEntra = builder.AddContainer("mock-entra", "ghcr.io/soluto/oidc-server-mock")
+ .WaitForCompletion(certExportExe)
+ .WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Password", certPassword)
+ .WithEnvironment("ASPNETCORE_Kestrel__Certificates__Default__Path", "/https/aspnetapp.pfx")
+ .WithBindMount("./dev-certificates", "/https")
+ .WithEnvironment("ASPNETCORE_URLS", "https://+:443")
+ .WithHttpsEndpoint(targetPort: 443);
+
+ mockEntra
+ .WithEnvironment("ASPNET_SERVICES_OPTIONS_INLINE", System.Text.Json.JsonSerializer.Serialize(new { BasePath = $"/{tenantId}/v2.0" }))
+ .WithEnvironment(
+ "CLIENTS_CONFIGURATION_INLINE",
+ () => System.Text.Json.JsonSerializer.Serialize(new[]
+ {
+ new
+ {
+ ClientId = clientId,
+ ClientName = "Sample App",
+ AllowedGrantTypes = new [] { "authorization_code" },
+ RedirectUris = new [] { vite.GetEndpoint("http").Url },
+ PostLogoutRedirectUris = new [] { vite.GetEndpoint("http").Url },
+ AllowedScopes = new [] { "openid", "profile", $"api://{clientId}/access_as_user" },
+ AllowOfflineAccess = true,
+ RequireClientSecret = false,
+ AlwaysSendClientClaims = true,
+ Claims = new [] {
+ new { Type = "aud", Value = $"api://{clientId}" },
+ new { Type = "ver", Value = $"1.0" },
+ },
+ ClientClaimsPrefix = string.Empty,
+ AlwaysIncludeUserClaimsInIdToken = true
+ }
+ }))
+ .WithEnvironment("USERS_CONFIGURATION_INLINE", () => System.Text.Json.JsonSerializer.Serialize(new[]
+ {
+ new
+ {
+ SubjectId = "1",
+ Username = "admin@test.com",
+ Password = "Password123",
+ Claims = new []
+ {
+ new { Type = "name", Value = "Frank Gardner" },
+ new { Type = "scp", Value = "access_as_user"},
+ }
+ }
+ }))
+ .WithEnvironment(
+ "SERVER_OPTIONS_INLINE",
+ () => JsonSerializer.Serialize(new
+ {
+ Cors = new
+ {
+ CorsPaths = new[]
+ {
+ $"/{tenantId}/v2.0/.well-known/openid-configuration",
+ $"/{tenantId}/v2.0/connect/token"
+ }
+ },
+ }))
+ .WithEnvironment(
+ "API_SCOPES_INLINE",
+ () => JsonSerializer.Serialize(new[]
+ {
+ new
+ {
+ Name = $"api://{clientId}/access_as_user",
+ UserClaims = new[]
+ {
+ "scp"
+ }
+ }
+ }));
+
+ api
+ .WithEnvironment("AzureAd__Instance", mockEntra.GetEndpoint("https"))
+ .WithEnvironment("AzureAd__ClientId", clientId)
+ .WithEnvironment("AzureAd__TenantId", tenantId);
}
builder.Build().Run();
diff --git a/MockOidcApp.AppHost/dev-certificates/.gitignore b/MockOidcApp.AppHost/dev-certificates/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/MockOidcApp.AppHost/dev-certificates/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/MockOidcApp.SpaIntegrationTests/playwright.config.ts b/MockOidcApp.SpaIntegrationTests/playwright.config.ts
index 518d871..923550a 100644
--- a/MockOidcApp.SpaIntegrationTests/playwright.config.ts
+++ b/MockOidcApp.SpaIntegrationTests/playwright.config.ts
@@ -30,6 +30,7 @@ export default defineConfig({
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'retain-on-failure',
+ ignoreHTTPSErrors: true,
},
/* Configure projects for major browsers */
diff --git a/MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts b/MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts
index ede41bf..45f67da 100644
--- a/MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts
+++ b/MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts
@@ -14,7 +14,11 @@ describe('Homepage', () => {
const button = await page.getByTestId("auth-button");
await button.waitFor();
await button.click();
- throw new Error("Not implemented - How can we login with Entra credentials when the login page could change at any time?");
+ const loginButton = page.getByRole('button', { name: 'Login' });
+ await loginButton.waitFor();
+ await page.getByRole('textbox', { name: 'Username' }).fill("admin@test.com");
+ await page.getByRole('textbox', { name: 'Password' }).fill("Password123");
+ await loginButton.click();
await expect(page.getByTestId("auth-button")).toHaveText("Logout");
await expect(page.getByTestId("welcome-message")).toHaveText("Hello Frank Gardner");
diff --git a/MockOidcApp.Vite/src/LoginButton.tsx b/MockOidcApp.Vite/src/LoginButton.tsx
index ce2b435..cf8e5c3 100644
--- a/MockOidcApp.Vite/src/LoginButton.tsx
+++ b/MockOidcApp.Vite/src/LoginButton.tsx
@@ -12,11 +12,11 @@ function LoginButton() {
})
.catch((error) => console.log(error));
};
+ const account = instance.getActiveAccount();
const handleLogout = () => {
- instance.logoutRedirect();
+ instance.logoutRedirect({ idTokenHint: account?.idToken });
};
- const account = instance.getActiveAccount();
return (
<>
diff --git a/MockOidcApp.Vite/src/authConfig.ts b/MockOidcApp.Vite/src/authConfig.ts
index 580dd7a..8ba530a 100644
--- a/MockOidcApp.Vite/src/authConfig.ts
+++ b/MockOidcApp.Vite/src/authConfig.ts
@@ -13,6 +13,7 @@ import { Configuration } from '@azure/msal-browser';
auth: {
clientId: settings.auth.clientId,
authority: settings.auth.authority,
+ knownAuthorities: [settings.auth.authority],
redirectUri: window.location.origin,
postLogoutRedirectUri: '/',
},
diff --git a/README.md b/README.md
index 851534e..d1aa9a3 100644
--- a/README.md
+++ b/README.md
@@ -12,29 +12,15 @@ The demo is written in C# and Typescript. To run the system:
1. [Node 18](https://nodejs.org/en/download/current)
1. Install recommended VSCode extensions
1. Install the dotnet development certificate by running the following command `dotnet dev-certs https --trust`
-1. Set up an Entra tenant and app registration for use by this Demo - (https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/1.%20Desktop%20app%20calls%20Web%20API#pre-requisites)
- 1. Ensure you have access to an Microsoft Entra Tenant. For more information on how to get a Microsoft Entra tenant, see [How to get a Microsoft Entra tenant](https://azure.microsoft.com/documentation/articles/active-directory-howto-tenant/)
- 1. [Register the sample application with your Entra tenant](https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/1.%20Desktop%20app%20calls%20Web%20API#step-2--register-the-sample-application-with-your-microsoft-entra-tenant)
- - Make a note of the TenantId and ClientId from your app registration
- 1. Create a new json file and add following content:
- ```json
- {
- "AzureAd": {
- "Instance": "https://login.microsoftonline.com",
- "ClientId": "",
- "TenantId": ""
- }
- }
- ```
- Save this file at either :
- - `~/.microsoft/usersecrets/5998d696-d9d8-48ef-b1ce-a783155ec3e4/secrets.json` on a linux/macOS system
- - `%APPDATA%\Microsoft\UserSecrets\5998d696-d9d8-48ef-b1ce-a783155ec3e4\secrets.json` on a windows system
1. Run the solution:
1. Via VSCode by opening the MockOidcApp.AppHost/Program.cs file and using the Play button that appears in the top left corner
- You may need to use the ".NET: Close Solution" and ".NET: Open Solution" commands in VSCode to enable this to work
1. Via Command line from the MockOidcApp.AppHost directory: `dotnet run`
1. Navigate to http://localhost:5100
-1. Use the Login and Logout button to observe how the demo application interacts with Entra and displays weather forecasts when logged in.
+1. Use the Login and Logout button to observe how the demo application interacts with a Mock identity service and displays weather forecasts when logged in.
+ - The demo is setup to have the following credentials:
+ - **Username:** `admin@test.com`
+ - **Password:** `Password123`
### Running integration tests
@@ -45,9 +31,8 @@ As described in the blog post there are integration tests provided, to run these
### Running GitHub actions
-A sample Github action workflow is provided to run the integration tests in Github actions. You can test these locally using [act](https://github.com/nektos/act):
+A sample Github action workflow is provided to run the integration tests in Github actions. Sadly you cannot test these locally using [act](https://github.com/nektos/act) due to an issue relating to the dev container bind mount. It may work when you try it though, you can attempt to run them using these instructions:
1. Open a Terminal in the root of the Repository
1. Run `act`
-At present one of the tests fail and therefore this workflow run will report itself failing.
\ No newline at end of file