Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/*
Expand Down
2 changes: 1 addition & 1 deletion MockOidcApp.Api/MockOidcApp.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.8.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.8.2" />
</ItemGroup>

<ItemGroup>
Expand Down
86 changes: 86 additions & 0 deletions MockOidcApp.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json;

var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddProject<Projects.MockOidcApp_Api>("api")
Expand All @@ -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 = "[email protected]",
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();
2 changes: 2 additions & 0 deletions MockOidcApp.AppHost/dev-certificates/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
1 change: 1 addition & 0 deletions MockOidcApp.SpaIntegrationTests/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
6 changes: 5 additions & 1 deletion MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';

Check failure on line 1 in MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts

View workflow job for this annotation

GitHub Actions / spa-test

[chromium] › tests/homepage.spec.ts:11:7 › has welcome {name} when authenticated

1) [chromium] › tests/homepage.spec.ts:11:7 › has welcome {name} when authenticated ────────────── Test timeout of 30000ms exceeded.
import { describe } from 'node:test';

describe('Homepage', () => {
Expand All @@ -14,7 +14,11 @@
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("[email protected]");

Check failure on line 19 in MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts

View workflow job for this annotation

GitHub Actions / spa-test

[chromium] › tests/homepage.spec.ts:11:7 › has welcome {name} when authenticated

1) [chromium] › tests/homepage.spec.ts:11:7 › has welcome {name} when authenticated ────────────── Error: locator.fill: Test timeout of 30000ms exceeded. Call log: - waiting for getByRole('textbox', { name: 'Username' }) 17 | const loginButton = page.getByRole('button', { name: 'Login' }); 18 | await loginButton.waitFor(); > 19 | await page.getByRole('textbox', { name: 'Username' }).fill("[email protected]"); | ^ 20 | await page.getByRole('textbox', { name: 'Password' }).fill("Password123"); 21 | await loginButton.click(); 22 | at /home/runner/work/MockOidcApp/MockOidcApp/MockOidcApp.SpaIntegrationTests/tests/homepage.spec.ts:19:59
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");
Expand Down
4 changes: 2 additions & 2 deletions MockOidcApp.Vite/src/LoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
Expand Down
1 change: 1 addition & 0 deletions MockOidcApp.Vite/src/authConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '/',
},
Expand Down
25 changes: 5 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 from previous step>",
"TenantId": "<ClientId from previous step>"
}
}
```
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:** `[email protected]`
- **Password:** `Password123`

### Running integration tests

Expand All @@ -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.
Loading