Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Hawxy committed Jul 26, 2024
1 parent 8ee9ca8 commit 38800fe
Show file tree
Hide file tree
Showing 12 changed files with 1,677 additions and 834 deletions.
6 changes: 0 additions & 6 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import { defineConfig } from 'vitepress'
import { BUNDLED_LANGUAGES } from 'shiki'

// Include `cs` as alias for csharp
BUNDLED_LANGUAGES
.find(lang => lang.id === 'csharp')!.aliases!.push('cs');


export default defineConfig({
title: 'Alba',
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface IAlbaExtension : IDisposable, IAsyncDisposable
/// <param name="host"></param>
/// <returns></returns>
Task Start(IAlbaHost host);

/// <summary>
/// Allow an extension to alter the application's
/// IHostBuilder prior to starting the application
Expand Down
19 changes: 19 additions & 0 deletions docs/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ the JWT tokens with a real Open Id Connect server **so you can test your service
The `JwtSecurityStub` will also honor the `WithClaim()` method to add additional claims on a scenario by scenario basis
as shown in the previous section.

## Override a specific scheme

Both `AuthenticationSecurityStub` and `JwtSecuritySnub` will replace all authentication schemes by default. If you only want a single scheme to be replaced,
you can pass the scheme name via the constructor:

<!-- snippet: sample_bootstrapping_with_stub_scheme_extension -->
<a id='snippet-sample_bootstrapping_with_stub_scheme_extension'></a>
```cs
// Stub out an individual scheme
var securityStub = new AuthenticationStub("custom")
.With("foo", "bar")
.With(JwtRegisteredClaimNames.Email, "[email protected]")
.WithName("jeremy");

await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba.Testing/Security/web_api_authentication_with_individual_stub.cs#L14-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_with_stub_scheme_extension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

## Integration with JWT Authentication

::: tip
Expand Down
4 changes: 2 additions & 2 deletions docs/scenarios/assertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ public interface IScenarioAssertion
void Assert(Scenario scenario, HttpContext context, ScenarioAssertionException ex);
}
```
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba/IScenarioAssertion.cs#L6-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_iscenarioassertion' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://github.com/JasperFx/alba/blob/master/src/Alba/IScenarioAssertion.cs#L5-L10' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_iscenarioassertion' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

As an example, here's the assertion from Alba that validates that the response body is supposed to

<!-- snippet: sample_BodyContainsAssertion -->
<a id='snippet-sample_bodycontainsassertion'></a>
```cs
internal class BodyContainsAssertion : IScenarioAssertion
internal sealed class BodyContainsAssertion : IScenarioAssertion
{
public string Text { get; set; }

Expand Down
2,353 changes: 1,541 additions & 812 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"docs-build": "npm-run-all -s mdsnippets vitepress-build"
},
"dependencies": {
"vitepress": "1.0.0-rc.20"
"vitepress": "1.3.1"
},
"devDependencies": {
"npm-run-all": "^4.1.5"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Net;
using System.Threading.Tasks;
using Alba.Security;
using Microsoft.IdentityModel.JsonWebTokens;
using Xunit;

namespace Alba.Testing.Security;

public class web_api_authentication_with_individual_stub
{
[Fact]
public async Task can_stub_individual_scheme()
{
#region sample_bootstrapping_with_stub_scheme_extension
// Stub out an individual scheme
var securityStub = new AuthenticationStub("custom")
.With("foo", "bar")
.With(JwtRegisteredClaimNames.Email, "[email protected]")
.WithName("jeremy");

await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);
#endregion

await host.Scenario(s =>
{
s.Get.Url("/identity2");
s.StatusCodeShouldBeOk();
});

await host.Scenario(s =>
{
s.Get.Url("/identity");
s.StatusCodeShouldBe(HttpStatusCode.Unauthorized);
});

}

[Fact]
public async Task can_stub_individual_scheme_jwt()
{
// This is a Alba extension that can "stub" out authentication
var securityStub = new JwtSecurityStub("custom")
.With("foo", "bar")
.With(JwtRegisteredClaimNames.Email, "[email protected]")
.WithName("jeremy");

// We're calling your real web service's configuration
await using var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);

await host.Scenario(s =>
{
s.Get.Url("/identity2");
s.StatusCodeShouldBeOk();
});

await host.Scenario(s =>
{
s.Get.Url("/identity");
s.StatusCodeShouldBe(HttpStatusCode.Unauthorized);
});

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ public async Task can_modify_claims_per_scenario()
}

#endregion


}
}
10 changes: 5 additions & 5 deletions src/Alba/Security/AuthenticationStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ public sealed class AuthenticationStub : AuthenticationExtensionBase, IAlbaExten
{
private const string TestSchemaName = "Test";

internal string? OverrideSchemaTargetName { get; }
internal string? OverrideSchemeTargetName { get; }

/// <summary>
/// Creates a new authentication stub. Will override all implementations by default.
/// </summary>
/// <param name="overrideSchemaTargetName">Override a specific authentication schema.</param>
public AuthenticationStub(string? overrideSchemaTargetName = null)
=> OverrideSchemaTargetName = overrideSchemaTargetName;
/// <param name="overrideSchemeTargetName">Override a specific authentication schema.</param>
public AuthenticationStub(string? overrideSchemeTargetName = null)
=> OverrideSchemeTargetName = overrideSchemeTargetName;

void IDisposable.Dispose()
{
Expand Down Expand Up @@ -57,7 +57,7 @@ private sealed class MockSchemeProvider : AuthenticationSchemeProvider
public MockSchemeProvider(AuthenticationStub authSchemaStub, IOptions<AuthenticationOptions> options)
: base(options)
{
_overrideSchemaTarget = authSchemaStub.OverrideSchemaTargetName;
_overrideSchemaTarget = authSchemaStub.OverrideSchemeTargetName;
}

public override Task<AuthenticationScheme?> GetSchemeAsync(string name)
Expand Down
20 changes: 16 additions & 4 deletions src/Alba/Security/JwtSecurityStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ namespace Alba.Security;
/// Use this extension to generate and apply JWT tokens to scenario requests using
/// a set of baseline claims
/// </summary>
public class JwtSecurityStub : AuthenticationExtensionBase, IAlbaExtension, IPostConfigureOptions<JwtBearerOptions>
public class JwtSecurityStub : AuthenticationExtensionBase, IAlbaExtension
{
private JwtBearerOptions? _options;

private readonly string? _overrideSchemaTargetName;
public JwtSecurityStub(string? overrideSchemaTargetName = null)
=> _overrideSchemaTargetName = overrideSchemaTargetName;

void IDisposable.Dispose()
{
// Nothing
Expand All @@ -38,7 +42,7 @@ Task IAlbaExtension.Start(IAlbaHost host)
{
// This seems to be necessary to "bake" in the JwtBearerOptions modifications
var options = host.Services.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>()
.Get("Bearer");
.Get(_overrideSchemaTargetName ?? JwtBearerDefaults.AuthenticationScheme);


host.BeforeEach(ConfigureJwt);
Expand All @@ -57,7 +61,15 @@ IHostBuilder IAlbaExtension.Configure(IHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>>(this);
if (_overrideSchemaTargetName != null)
{
services.PostConfigure<JwtBearerOptions>(_overrideSchemaTargetName, PostConfigure);
}
else
{
services.PostConfigureAll<JwtBearerOptions>(PostConfigure);
}
});
}

Expand Down Expand Up @@ -103,7 +115,7 @@ protected override IEnumerable<Claim> stubTypeSpecificClaims()
}
}

void IPostConfigureOptions<JwtBearerOptions>.PostConfigure(string? name, JwtBearerOptions options)
void PostConfigure(JwtBearerOptions options)
{
// This will deactivate the callout to the OIDC server
options.ConfigurationManager =
Expand Down
11 changes: 11 additions & 0 deletions src/WebAppSecuredWithJwt/IdentityController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,15 @@ public IActionResult Get()
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}

[Route("identity2")]
[Authorize(AuthenticationSchemes = "custom")]
public class Identity2Controller : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
}
20 changes: 18 additions & 2 deletions src/WebAppSecuredWithJwt/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public void ConfigureServices(IServiceCollection services)
});

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

.AddJwtBearer(options =>
{
// A real application would pull all this information from configuration
Expand All @@ -50,14 +49,31 @@ public void ConfigureServices(IServiceCollection services)
// tests
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("some really big key that should work")),
NameClaimType = ClaimTypes.NameIdentifier
};
}).AddJwtBearer("custom", options =>
{
// A real application would pull all this information from configuration
// of course, but I'm hardcoding it in testing
options.Audience = "jwtsample";
options.ClaimsIssuer = "myapp";
// don't worry about this, our JwtSecurityStub is gonna switch it off in
// tests
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("some really big key that should work")),
NameClaimType = ClaimTypes.NameIdentifier
};
});

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down

0 comments on commit 38800fe

Please sign in to comment.