In the last three lessons the Kladde got many permission decisions: the API demands a sign-in, internal pages do too, only the manager deletes, the appointment booking is public. Each of these decisions sits in a different place, in an attribute, a line in Program.cs, an _Imports.razor. If one of them drops out during a refactoring, nobody notices: the app starts, all pages work, and only a stranger notices that he can delete contacts.

Broken access control is number 1 in the OWASP Top 10 (lesson 2.6.6). In this lesson you therefore secure the Kladde's permissions with two kinds of tests: a matrix that checks the expected result for every important request and every person, and a permissions test over all endpoints that turns red as soon as an endpoint without a permission decision is added.

The test scheme under the magnifying glass

Since lesson 2.6.1, every test request signs in through the scheme Test, with a name in X-Test-User and, since lesson 2.6.3, with roles in X-Test-Roles. A sign-in scheme is a class that inherits from AuthenticationHandler<TOptions> and answers one question: Who is sending this request? The answer is one of three results:

Result Meaning In the test scheme
AuthenticateResult.Success(ticket) The person is recognized; the ticket contains their ClaimsPrincipal. header X-Test-User present
AuthenticateResult.NoResult() This scheme knows nothing about the request; it stays anonymous. header missing
AuthenticateResult.Fail(…) There was proof, but it's invalid, such as a wrong signature. doesn't occur

What's important is what the test scheme replaces. The factory sets only DefaultAuthenticateScheme, the scheme for recognizing:

C#
services.Configure<AuthenticationOptions>(options =>
    options.DefaultAuthenticateScheme = TestAuthHandler.SchemeName);

For the challenge (no sign-in) and the forbid (no permission), the Kladde's default scheme stays responsible, the cookie scheme. That's why in a test, pages answer with exactly the same redirect to /sign-in or /no-access as in production, and the API with 401 and 403. Anyone who sets AddAuthentication("Test") as the default for everything instead gets a 401 everywhere in the test, pages too, and tests different behavior from what people will see later.

Two rules go with this scheme:

  • It lives only in the test project. There's nothing of it in Program.cs, not even behind if (app.Environment.IsDevelopment()). A scheme that believes anyone who sends a header is an open door in production, and a wrongly set environment is enough to push it open.
  • It doesn't test a sign-in, only permissions. Whether signing in with Entra ID works is shown by the tests from lesson 2.6.2. Here the question is: What may a person with these roles do?