Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create KeycloakRealmResource to represent the address of a realm #5092

Open
paulomorgado opened this issue Jul 26, 2024 · 6 comments · May be fixed by #7120
Open

Create KeycloakRealmResource to represent the address of a realm #5092

paulomorgado opened this issue Jul 26, 2024 · 6 comments · May be fixed by #7120
Labels
area-integrations Issues pertaining to Aspire Integrations packages keycloak Issues related to keycloack integrations
Milestone

Comments

@paulomorgado
Copy link
Contributor

Background and Motivation

A Keycloak server may have several realms and the realms are what's important for applications.

But, at the moment, JwtBearerOptions does not support service discovery for authority and Keycloak has its own way of constructing the base address of the realm.

It would be helpful, if "creating" a realm would create connection string for the realm.

Proposed API

On the Aspire host, there would be a AddReal method that would create a connections string to the realm.

var keycloak = builder.AddKeycloak(...);
var realm = keycloak.AddRealm(...);

var service = builder.AddProject<...>(...)
    .WithEnvironment("Security__Authentication__JwtBearerOptions__Authority", realm)
    :

Usage Examples

On the service side, the service would only have to register the configuration:

builder.Services
    .AddOptions<JwtBearerOptions>("Bearer")
    .BindConfiguration("VisionBox:Security:Authentication:JwtBearerOptions")
    .ValidateOnStart();
-->

## Complementary Designs

Import realm could return a connection string for the realm.
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Jul 26, 2024
@davidfowl davidfowl added area-integrations Issues pertaining to Aspire Integrations packages enhancement and removed area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication labels Sep 7, 2024
@davidfowl davidfowl added the keycloak Issues related to keycloack integrations label Sep 29, 2024
@joperezr joperezr added the untriaged New issue has not been triaged label Oct 15, 2024
@eerhardt eerhardt added this to the Backlog milestone Jan 14, 2025
@eerhardt
Copy link
Member

@julioct @DamianEdwards - any thoughts on this?

@paulomorgado
Copy link
Contributor Author

I have an implementation that I can submit.

@eerhardt eerhardt removed the untriaged New issue has not been triaged label Jan 14, 2025
@DamianEdwards
Copy link
Member

Yeah I ran into this while building out the sample too and did some things over there. Modeling realms in a first-class way (e.g. as child resources) seems like a reasonable approach to explore but I think it will take some iteration to land on something.

@paulomorgado
Copy link
Contributor Author

This is what I have now:

/// <summary>
/// Adds a Keycloak Realm to the application model from a <see cref="IResourceBuilder{KeycloakRealmResource}"/>.
/// </summary>
/// <param name="builder">The Keycloak server resource builder.</param>
/// <param name="name">The name of the realm.</param>
/// <param name="realmName">The name of the realm. If not provided, the resource name will be used.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{KeycloakRealmResource}"/>.</returns>
public static IResourceBuilder<KeycloakRealmResource> AddRealm(
    this IResourceBuilder<KeycloakResource> builder,
    string name,
    string? realmName = null)
{
    ArgumentNullException.ThrowIfNull(builder);

    // Use the resource name as the realm name if it's not provided
    realmName ??= name;

    var keycloakRealm = new KeycloakRealmResource(name, realmName, builder.Resource);

    return builder.ApplicationBuilder.AddResource(keycloakRealm);
}

/// <summary>
/// Represents a Keycloak Realm resource.
/// </summary>
/// <param name="name">The name of the realm resource.</param>
/// <param name="realmName">The name of the realm.</param>
/// <param name="parent">The Keycloak server resource associated with this database.</param>
public sealed class KeycloakRealmResource(string name, string realmName, KeycloakResource parent) : Resource(name), IResourceWithParent<KeycloakResource>, IResourceWithConnectionString
{
    private EndpointReference? _parentEndpoint;
    private EndpointReference ParentEndpoint => _parentEndpoint ??= new(Parent, "http");

    /// <inheritdoc/>
    public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create(
        $"{ParentEndpoint.Property(EndpointProperty.Url)}/realms/{RealmName}");

    /// <inheritdoc/>
    public KeycloakResource Parent { get; } = parent;

    /// <summary>
    /// Gets the name of the realm.
    /// </summary>
    public string RealmName { get; } = realmName;
}

I don't use any Keycloak Aspire components in my applications.

Usually, it's just used like this:

resourcebuild.WithEnvironment("JwtBearerOptions__Authority", realm);

But, sometimes the application already has configuration for providers without .well-known/openid-configuration endpoint and I need to configure everything:

resourcebuild
    .WithEnvironment(ctx =>
    {
        var baseAddress = idp.Resource.ConnectionStringExpression;
        ctx.EnvironmentVariables["JwtBearerOptions__Authority"] = baseAddress;
        ctx.EnvironmentVariables["JwtBearerOptions__MetadataAddress"] = ReferenceExpression.Create($"{baseAddress}/.well-known/openid-configuration");
        ctx.EnvironmentVariables["JwtBearerOptions__Configuration__Issuer"] = baseAddress;
        ctx.EnvironmentVariables["JwtBearerOptions__Configuration__AuthorizationEndpoint"] = ReferenceExpression.Create($"{baseAddress}/protocol/openid-connect/auth");
        ctx.EnvironmentVariables["JwtBearerOptions__Configuration__TokenEndpoint"] = ReferenceExpression.Create($"{baseAddress}/protocol/openid-connect/token");
        ctx.EnvironmentVariables["JwtBearerOptions__Configuration__UserInfoEndpoint"] = ReferenceExpression.Create($"{baseAddress}/protocol/openid-connect/userinfo");
        ctx.EnvironmentVariables["JwtBearerOptions__RequireHttpsMetadata"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__RequireSignedTokens"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__ValidateAudience"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__ValidateActor"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__ValidateIssuer"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__ValidateIssuerSigningKey"] = "false";
        ctx.EnvironmentVariables["JwtBearerOptions__TokenValidationParameters__ValidateLifetime"] = "false";
    })
    ;

One improvement would be to create properties and expressions for all these paths.

If it's OK with you, I can submit a PR with this, and we'll go from there.

@DamianEdwards
Copy link
Member

If it's OK with you, I can submit a PR with this, and we'll go from there.

I don't think we're ready to work on a PR for this right now to be honest. This is an important area that we want to return to but Keycloak improvements aren't in scope for the next minor release or two so a PR at this point might not get much attention. That said, a draft PR from a fork where we can explore ideas together isn't at all a bad thing, just setting expectations.

@paulomorgado
Copy link
Contributor Author

I've created a draft PR for this: #7120 for discussion and for those that need it to pick it up.

Keycloak integration is still in preview. We can iterate on it.

@davidfowl davidfowl linked a pull request Jan 15, 2025 that will close this issue
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-integrations Issues pertaining to Aspire Integrations packages keycloak Issues related to keycloack integrations
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants