Skip to content

Security Extensions

Stub out all authentication

To just stub out all possible authentication inside your ASP.NET Core system in testing, you can use the new AuthenticationStub to automatically authenticate every request and build out a ClaimsPrincipal to your specification.

Here's a sample of bootstrapping an AlbaHost with the AuthenticationStub:

cs
// This is a Alba extension that can "stub" out authentication
var securityStub = new AuthenticationStub()
    .With("foo", "bar")
    .With(JwtRegisteredClaimNames.Email, "guy@company.com")
    .WithName("jeremy");

// We're calling your real web service's configuration
theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);
// This is a Alba extension that can "stub" out authentication
var securityStub = new AuthenticationStub()
    .With("foo", "bar")
    .With(JwtRegisteredClaimNames.Email, "guy@company.com")
    .WithName("jeremy");

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

snippet source | anchor

When you need to test scenarios with different claims than the "baseline" claims defined on the AuthenticationStub object you created in the AlbaHost setup, you can use the WithClaim() method as shown below to add additional claims to the principal for only one scenario as shown below:

cs
[Fact]
public async Task can_modify_claims_per_scenario()
{
    var input = new Numbers
    {
        Values = new[] {2, 3, 4}
    };

    var response = await theHost.Scenario(x =>
    {
        // This is a custom claim that would only be used for the 
        // JWT token in this individual test
        x.WithClaim(new Claim("color", "green"));
        x.RemoveClaim("foo");
        x.Post.Json(input).ToUrl("/math");
        x.StatusCodeShouldBeOk();
    });

    var principal = response.Context.User;
    principal.ShouldNotBeNull();
    
    principal.Claims.Single(x => x.Type == "color")
        .Value.ShouldBe("green");

    principal.Claims.Any(x => x.Type.Equals("foo")).ShouldBeFalse();
}
[Fact]
public async Task can_modify_claims_per_scenario()
{
    var input = new Numbers
    {
        Values = new[] {2, 3, 4}
    };

    var response = await theHost.Scenario(x =>
    {
        // This is a custom claim that would only be used for the 
        // JWT token in this individual test
        x.WithClaim(new Claim("color", "green"));
        x.RemoveClaim("foo");
        x.Post.Json(input).ToUrl("/math");
        x.StatusCodeShouldBeOk();
    });

    var principal = response.Context.User;
    principal.ShouldNotBeNull();
    
    principal.Claims.Single(x => x.Type == "color")
        .Value.ShouldBe("green");

    principal.Claims.Any(x => x.Type.Equals("foo")).ShouldBeFalse();
}

snippet source | anchor

Stub out JWT authentication

If you have an application that uses JWT, bearer tokens as your primary means of authentication, you can use the new JwtSecurityStub to automatically add a valid JWT token string to the HTTP Authorization header before any scenarios are executed. Similar to the AuthenticationStub, the JwtSecurityStub allows you to specify a baseline set of claims that should be in the JWT token:

cs
// This is a new Alba extension that can "stub" out
// JWT token authentication
var jwtSecurityStub = new JwtSecurityStub()
    .With("foo", "bar")
    .With(JwtRegisteredClaimNames.Email, "guy@company.com");

// We're calling your real web service's configuration
theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(jwtSecurityStub);
// This is a new Alba extension that can "stub" out
// JWT token authentication
var jwtSecurityStub = new JwtSecurityStub()
    .With("foo", "bar")
    .With(JwtRegisteredClaimNames.Email, "guy@company.com");

// We're calling your real web service's configuration
theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(jwtSecurityStub);

snippet source | anchor

The JwtSecurityStub reaches into your application's configuration to find the security signing key for JWT tokens, and uses that key to sign the keys that it generates. The JwtSecurityStub will also disable any callouts to validate the JWT tokens with a real Open Id Connect server so you can test your service without an external token identity server running.

The JwtSecurityStub will also honor the WithClaim() method to add additional claims on a scenario by scenario basis as shown in the previous section.

Integration with JWT Authentication

TIP

All of these extensions depend on the JwtBearerOptions configuration from your application. The extensions will use the Authority property of JwtBearerOptions for the Url of the OIDC token server.

If you want to test your ASP.NET Core web services that are authenticated by an Open Id Connect workflow and you also want to be testing through the authentication from the real OIDC identity server, Alba v5 comes with new extensions to automatically fetch and apply JWT tokens to scenario tests.

To use the OIDC Client Credentials workflow, you can use the OpenConnectClientCredentials extension:

cs
oidc = new OpenConnectClientCredentials
{
    // These three properties are mandatory, and
    // would refer to matching configuration in your
    // OIDC server
    ClientId = Config.ClientId,
    ClientSecret = Config.ClientSecret,
    Scope = Config.ApiScope
};

theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(x =>
{
    x.ConfigureServices((ctx, collection) =>
        collection.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
            options =>
            {
                options.Authority = _fixture.IdentityServer.BaseAddress.ToString();
                options.BackchannelHttpHandler = _fixture.IdentityServer.CreateHandler();
                options.RequireHttpsMetadata = false;
            }));
}, oidc);
oidc = new OpenConnectClientCredentials
{
    // These three properties are mandatory, and
    // would refer to matching configuration in your
    // OIDC server
    ClientId = Config.ClientId,
    ClientSecret = Config.ClientSecret,
    Scope = Config.ApiScope
};

theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(x =>
{
    x.ConfigureServices((ctx, collection) =>
        collection.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
            options =>
            {
                options.Authority = _fixture.IdentityServer.BaseAddress.ToString();
                options.BackchannelHttpHandler = _fixture.IdentityServer.CreateHandler();
                options.RequireHttpsMetadata = false;
            }));
}, oidc);

snippet source | anchor

To use the OIDC Resource Owner Password Grant workflow, you can use the OpenConnectUserPassword extension:

cs
oidc = new OpenConnectUserPassword
{
    // All of these properties are mandatory
    ClientId = Config.ClientId,
    ClientSecret = Config.ClientSecret,
    UserName = "alice",
    Password = "alice"
};

theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(x =>
{
    x.ConfigureServices((ctx, collection) =>
        collection.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
            options =>
            {
                options.Authority = _fixture.IdentityServer.BaseAddress.ToString();
                options.BackchannelHttpHandler = _fixture.IdentityServer.CreateHandler();
                options.RequireHttpsMetadata = false;
            }));
}, oidc);
oidc = new OpenConnectUserPassword
{
    // All of these properties are mandatory
    ClientId = Config.ClientId,
    ClientSecret = Config.ClientSecret,
    UserName = "alice",
    Password = "alice"
};

theHost = await AlbaHost.For<WebAppSecuredWithJwt.Program>(x =>
{
    x.ConfigureServices((ctx, collection) =>
        collection.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme,
            options =>
            {
                options.Authority = _fixture.IdentityServer.BaseAddress.ToString();
                options.BackchannelHttpHandler = _fixture.IdentityServer.CreateHandler();
                options.RequireHttpsMetadata = false;
            }));
}, oidc);

snippet source | anchor

With the OpenConnectUserPassword extension, you can also use a different user name and password for a single scenario with the Scenario.UserAndPasswordIs(user, password) extension method as shown below:

cs
[Fact]
public async Task post_to_a_secured_endpoint_with_jwt_with_overridden_user_and_password()
{
    // Building the input body
    var input = new Numbers
    {
        Values = new[] {2, 3, 4}
    };

    var response = await theHost.Scenario(x =>
    {
        // Alba deals with Json serialization for us
        x.Post.Json(input).ToUrl("/math");
        
        // Override the user credentials for just this scenario
        x.UserAndPasswordIs("bob", "bob");
        
        // Enforce that the HTTP Status Code is 200 Ok
        x.StatusCodeShouldBeOk();
    });

    var output = response.ReadAsJson<Result>();
    output.Sum.ShouldBe(9);
    output.Product.ShouldBe(24);

    var user = response.Context.User;
    user.FindFirst("name").Value.ShouldBe("Bob Smith");
}
[Fact]
public async Task post_to_a_secured_endpoint_with_jwt_with_overridden_user_and_password()
{
    // Building the input body
    var input = new Numbers
    {
        Values = new[] {2, 3, 4}
    };

    var response = await theHost.Scenario(x =>
    {
        // Alba deals with Json serialization for us
        x.Post.Json(input).ToUrl("/math");
        
        // Override the user credentials for just this scenario
        x.UserAndPasswordIs("bob", "bob");
        
        // Enforce that the HTTP Status Code is 200 Ok
        x.StatusCodeShouldBeOk();
    });

    var output = response.ReadAsJson<Result>();
    output.Sum.ShouldBe(9);
    output.Product.ShouldBe(24);

    var user = response.Context.User;
    user.FindFirst("name").Value.ShouldBe("Bob Smith");
}

snippet source | anchor

Windows Authentication

To add support for windows authentication in your Alba specifications, we recommend checking out the AspNetCore.TestHost.WindowsAuth project.