diff --git a/src/Ignis.Auth/AuthConstants.cs b/src/Ignis.Auth/AuthConstants.cs
new file mode 100644
index 0000000..da943f9
--- /dev/null
+++ b/src/Ignis.Auth/AuthConstants.cs
@@ -0,0 +1,9 @@
+namespace Ignis.Auth;
+
+public static class AuthConstants
+{
+ ///
+ /// Authentication scheme used for the user session cookie during the authorization code flow.
+ ///
+ public const string SessionScheme = "IgnisAuth.Session";
+}
diff --git a/tests/Ignis.Api.Tests/AuthConfigurationTests.cs b/tests/Ignis.Api.Tests/AuthConfigurationTests.cs
index 37f476e..c36dbf9 100644
--- a/tests/Ignis.Api.Tests/AuthConfigurationTests.cs
+++ b/tests/Ignis.Api.Tests/AuthConfigurationTests.cs
@@ -123,6 +123,7 @@ public async Task TokenEndpoint_Available_WhenAuthEnabled()
["AuthSettings__Clients__0__ClientId"] = "config-client",
["AuthSettings__Clients__0__ClientSecret"] = "config-secret",
["AuthSettings__Clients__0__DisplayName"] = "Config Client",
+ ["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["StoreSettings__ConnectionString"] = _connectionString,
};
SetEnvVars(envVars);
@@ -139,6 +140,7 @@ public async Task TokenEndpoint_Available_WhenAuthEnabled()
["AuthSettings:Clients:0:ClientId"] = "config-client",
["AuthSettings:Clients:0:ClientSecret"] = "config-secret",
["AuthSettings:Clients:0:DisplayName"] = "Config Client",
+ ["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
});
using var client = factory.CreateClient();
@@ -174,6 +176,7 @@ public async Task TokenEndpoint_Works_WithCertificatesInProduction()
["AuthSettings__Clients__0__ClientId"] = "cert-client",
["AuthSettings__Clients__0__ClientSecret"] = "cert-secret",
["AuthSettings__Clients__0__DisplayName"] = "Cert Client",
+ ["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["AuthSettings__Certificates__SigningCertificatePath"] = signingCertPath,
["AuthSettings__Certificates__SigningCertificatePassword"] = signingCertPassword,
["AuthSettings__Certificates__EncryptionCertificatePath"] = encryptionCertPath,
@@ -194,6 +197,7 @@ public async Task TokenEndpoint_Works_WithCertificatesInProduction()
["AuthSettings:Clients:0:ClientId"] = "cert-client",
["AuthSettings:Clients:0:ClientSecret"] = "cert-secret",
["AuthSettings:Clients:0:DisplayName"] = "Cert Client",
+ ["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
["AuthSettings:Certificates:SigningCertificatePath"] = signingCertPath,
["AuthSettings:Certificates:SigningCertificatePassword"] = signingCertPassword,
["AuthSettings:Certificates:EncryptionCertificatePath"] = encryptionCertPath,
@@ -235,6 +239,7 @@ public void Startup_Fails_WhenCertificatesMissing_InProduction()
["AuthSettings__ConnectionString"] = _connectionString,
["AuthSettings__Clients__0__ClientId"] = "cert-client",
["AuthSettings__Clients__0__ClientSecret"] = "cert-secret",
+ ["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["StoreSettings__ConnectionString"] = _connectionString,
};
SetEnvVars(envVars);
@@ -252,6 +257,7 @@ public void Startup_Fails_WhenCertificatesMissing_InProduction()
["AuthSettings:ConnectionString"] = _connectionString,
["AuthSettings:Clients:0:ClientId"] = "cert-client",
["AuthSettings:Clients:0:ClientSecret"] = "cert-secret",
+ ["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
}, environment: "Production");
factory.CreateClient();
};
diff --git a/tests/Ignis.Api.Tests/AuthorizationControllerTests.cs b/tests/Ignis.Api.Tests/AuthorizationControllerTests.cs
index 3de3f8c..d112332 100644
--- a/tests/Ignis.Api.Tests/AuthorizationControllerTests.cs
+++ b/tests/Ignis.Api.Tests/AuthorizationControllerTests.cs
@@ -8,11 +8,11 @@ namespace Ignis.Api.Tests;
[Collection("IntegrationTests")]
public class AuthorizationControllerTests : IClassFixture
{
- private readonly HttpClient _client;
+ private readonly IntegrationFixture _fixture;
public AuthorizationControllerTests(IntegrationFixture fixture)
{
- _client = fixture.Factory.CreateClient();
+ _fixture = fixture;
}
private static CancellationToken CT => TestContext.Current.CancellationToken;
@@ -20,7 +20,9 @@ public AuthorizationControllerTests(IntegrationFixture fixture)
[Fact]
public async Task Token_WithValidClientCredentials_ReturnsAccessToken()
{
- var response = await _client.PostAsync("/connect/token",
+ using var client = _fixture.Factory.CreateClient();
+
+ var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "client_credentials",
@@ -38,7 +40,9 @@ public async Task Token_WithValidClientCredentials_ReturnsAccessToken()
[Fact]
public async Task Token_WithInvalidClient_ReturnsUnauthorized()
{
- var response = await _client.PostAsync("/connect/token",
+ using var client = _fixture.Factory.CreateClient();
+
+ var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "client_credentials",
@@ -52,7 +56,9 @@ public async Task Token_WithInvalidClient_ReturnsUnauthorized()
[Fact]
public async Task Token_WithWrongSecret_ReturnsUnauthorized()
{
- var response = await _client.PostAsync("/connect/token",
+ using var client = _fixture.Factory.CreateClient();
+
+ var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "client_credentials",
@@ -66,7 +72,9 @@ public async Task Token_WithWrongSecret_ReturnsUnauthorized()
[Fact]
public async Task Token_WithUnsupportedGrantType_ReturnsBadRequest()
{
- var response = await _client.PostAsync("/connect/token",
+ using var client = _fixture.Factory.CreateClient();
+
+ var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary
{
["grant_type"] = "password",
diff --git a/tests/Ignis.Api.Tests/IgnisApiFactory.cs b/tests/Ignis.Api.Tests/IgnisApiFactory.cs
index 4149bd7..5eadc9e 100644
--- a/tests/Ignis.Api.Tests/IgnisApiFactory.cs
+++ b/tests/Ignis.Api.Tests/IgnisApiFactory.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
namespace Ignis.Api.Tests;
@@ -28,9 +29,16 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
["AuthSettings:Clients:0:ClientId"] = "test-client",
["AuthSettings:Clients:0:ClientSecret"] = "test-secret",
["AuthSettings:Clients:0:DisplayName"] = "Test Client",
+ ["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
+ ["AuthSettings:Clients:0:RedirectUris:0"] = "http://localhost/callback",
});
});
+ builder.ConfigureServices(services =>
+ {
+ services.AddSingleton();
+ });
+
builder.UseEnvironment("Development");
}
}
diff --git a/tests/Ignis.Api.Tests/IntegrationFixture.cs b/tests/Ignis.Api.Tests/IntegrationFixture.cs
index 6d640d6..0367bb6 100644
--- a/tests/Ignis.Api.Tests/IntegrationFixture.cs
+++ b/tests/Ignis.Api.Tests/IntegrationFixture.cs
@@ -30,6 +30,19 @@ private static string BuildConnectionString(string raw)
return mongoUrl.ToString();
}
+ private static readonly string[] EnvVarKeys =
+ [
+ "StoreSettings__ConnectionString",
+ "AuthSettings__Enabled",
+ "AuthSettings__ConnectionString",
+ "AuthSettings__Clients__0__ClientId",
+ "AuthSettings__Clients__0__ClientSecret",
+ "AuthSettings__Clients__0__DisplayName",
+ "AuthSettings__Clients__0__AllowedGrantTypes__0",
+ "AuthSettings__Clients__0__AllowedGrantTypes__1",
+ "AuthSettings__Clients__0__RedirectUris__0",
+ ];
+
public async ValueTask InitializeAsync()
{
await _mongo.StartAsync();
@@ -41,18 +54,16 @@ public async ValueTask InitializeAsync()
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientId", "test-client");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientSecret", "test-secret");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__DisplayName", "Test Client");
+ Environment.SetEnvironmentVariable("AuthSettings__Clients__0__AllowedGrantTypes__0", "client_credentials");
+ Environment.SetEnvironmentVariable("AuthSettings__Clients__0__RedirectUris__0", "http://localhost/callback");
Factory = new IgnisApiFactory(connectionString);
}
public async ValueTask DisposeAsync()
{
- Environment.SetEnvironmentVariable("StoreSettings__ConnectionString", null);
- Environment.SetEnvironmentVariable("AuthSettings__Enabled", null);
- Environment.SetEnvironmentVariable("AuthSettings__ConnectionString", null);
- Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientId", null);
- Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientSecret", null);
- Environment.SetEnvironmentVariable("AuthSettings__Clients__0__DisplayName", null);
+ foreach (var key in EnvVarKeys)
+ Environment.SetEnvironmentVariable(key, null);
Factory.Dispose();
await _mongo.DisposeAsync();
}
diff --git a/tests/Ignis.Api.Tests/TestLoginStartupFilter.cs b/tests/Ignis.Api.Tests/TestLoginStartupFilter.cs
new file mode 100644
index 0000000..1f795ec
--- /dev/null
+++ b/tests/Ignis.Api.Tests/TestLoginStartupFilter.cs
@@ -0,0 +1,43 @@
+using System.Security.Claims;
+
+using Ignis.Auth;
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+
+namespace Ignis.Api.Tests;
+
+///
+/// Adds a /test-login endpoint that signs in as a test user
+/// using the cookie scheme.
+///
+internal sealed class TestLoginStartupFilter : IStartupFilter
+{
+ public Action Configure(Action next)
+ {
+ return app =>
+ {
+ app.Use(async (context, nextMiddleware) =>
+ {
+ if (context.Request.Path != "/test-login")
+ {
+ await nextMiddleware();
+ return;
+ }
+
+ var claims = new List
+ {
+ new(ClaimTypes.NameIdentifier, "test-user-id"),
+ new(ClaimTypes.Name, "Test User"),
+ };
+ var identity = new ClaimsIdentity(claims, AuthConstants.SessionScheme);
+ await context.SignInAsync(
+ AuthConstants.SessionScheme, new ClaimsPrincipal(identity));
+ context.Response.StatusCode = StatusCodes.Status200OK;
+ });
+ next(app);
+ };
+ }
+}