Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keycloak client for ASP.NET Core

Is there any existing Keycloak client for Asp.net Core? I have found a NuGet package for .net but it doesn't work with Core. Do you have any ideas how to easily integrate with this security server (or maybe using any other alternatives)?

like image 962
mikes Avatar asked Jan 18 '17 13:01

mikes


3 Answers

I've played a bit with this today. The most straightforward way is too use OpenId standard.

In Startup.cs I used OpenIdConnect Authentication:

    public void Configure(...)
    { (...)
         app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
            AutomaticAuthenticate = true,
            CookieHttpOnly = true,
            CookieSecure = CookieSecurePolicy.SameAsRequest
        });
        app.UseOpenIdConnectAuthentication(CreateKeycloakOpenIdConnectOptions());`(...)
 }`

OpenIdConnectOptions method:

private OpenIdConnectOptions CreateKeycloakOpenIdConnectOptions()
    {
        var options = new OpenIdConnectOptions
        {
            AuthenticationScheme = "oidc",
            SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
            Authority = Configuration["Authentication:KeycloakAuthentication:ServerAddress"]+"/auth/realms/"+ Configuration["Authentication:KeycloakAuthentication:Realm"],
            RequireHttpsMetadata = false, //only in development
            PostLogoutRedirectUri = Configuration["Authentication:KeycloakAuthentication:PostLogoutRedirectUri"],
            ClientId = Configuration["Authentication:KeycloakAuthentication:ClientId"],
            ClientSecret = Configuration["Authentication:KeycloakAuthentication:ClientSecret"],
            ResponseType = OpenIdConnectResponseType.Code,
            GetClaimsFromUserInfoEndpoint = true,
            SaveTokens = true

        };
        options.Scope.Add("openid");
        return options;
    }

In appsettings.json add configuration for Keycloak:

{
  (...),
  "Authentication": {
    "KeycloakAuthentication": {
      "ServerAddress": "http://localhost:8180",
      "Realm": "demo",
      "PostLogoutRedirectUri": "http://localhost:57630/",
      "ClientId": "KeycloakASPNETCore",
      "ClientSecret": "secret-get-it-in-keycloakConsole-client-credentials"
    }
  }
}

Keycloak client is configuerd as followed:

  • Client settings,
  • I've added 'accounting' role for test,
  • I added mapper 'member_of' of type 'User Client Role' for roles so that roles are added in the claims

If I want to Authorize user by role I do something like this:

Add authorization by claims in ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
    {
        (...)

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Accounting", policy =>
            policy.RequireClaim("member_of", "[accounting]")); //this claim value is an array. Any suggestions how to extract just single role? This still works.
        });
    }

I've edited get method in ValuesController (Default Web API template):

[Authorize(Policy = "Accounting")]
[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET api/values        
    [HttpGet]
    public Dictionary<string,string> Get()
    {
        var userPrinciple = User as ClaimsPrincipal;
        var claims = new Dictionary<string, string>();

        foreach (var claim in userPrinciple.Claims)
        {
            var key = claim.Type;
            var value = claim.Value;

            claims.Add(key, value);
        }


        return claims;
    }

If I login with user that has accounting role or is in group that has accounting role, it should display my user claims on address localhost:57630/api/values.

I hope this works for you.

Edit: .NET Core 2 Hi everyone! The way my app works changed quite a bit and I have not fully tested .NET Core 2 yet, but you can still try connecting to Keycloak like this in ConfigureServices:

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {

                options.Authority = Configuration["Authentication:KeycloakAuthentication:ServerAddress"] + "/auth/realms/" + Configuration["Authentication:KeycloakAuthentication:Realm"];
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidAudiences = new string[] { "curl", "financeApplication", "accountingApplication", "swagger"}
                };
                options.RequireHttpsMetadata = false; //for test only!
                options.SaveToken = true;
                options.Validate();

            });

And in Configure:

app.UseAuthentication();

You can access your token later with IHttpContextAccessor httpContextAccessor, for example:

public KeycloakAuthorizationRequirementHandler(IConfiguration config,
            IHttpContextAccessor httpContextAccessor,
            IMemoryCache memoryCache)
        {
            _config = config;
            _httpContextAccessor = httpContextAccessor;
            _memoryCache = memoryCache;
        }

//get accessToken

var accessToken = _httpContextAccessor.HttpContext.GetTokenAsync("access_token");

_httpContextAccessor.HttpContext.Items["username"] = username;

Tell me how it goes.

like image 160
frogec Avatar answered Nov 14 '22 20:11

frogec


If you want to use standard .Net Role mappings with Keycloak Client Roles, setup like so:

Startup.cs:

    services.AddAuthorization(options =>
    {
        options.AddPolicy("Users", policy =>
        policy.RequireRole("Users"));
    });

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.Authority = Configuration["Authentication:oidc:Authority"]
        options.ClientId = Configuration["Authentication:oidc:ClientId"];
        options.ClientSecret = Configuration["Authentication:oidc:ClientSecret"];
        options.RequireHttpsMetadata = false;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.SaveTokens = true;
        options.RemoteSignOutPath = "/SignOut";
        options.SignedOutRedirectUri = "Redirect-here";
        options.ResponseType = "code";

    });

appsettings.json:

  "Authentication": {
    "oidc": {
      "Authority":"http://your-keycloak-server/auth/realms/your-realm",
      "ClientId":"Your-Client-Name",
      "ClientSecret":"Your-client-secret"
    }
  }

Keycloak Client Settings:

  • Create new Token Mapper
  • Mapper-Values (enter your own client name)

Now you can use standard authorize role statements to apply your Keycloak Client Roles to your ASP.NET project:

[Authorize(Roles = "Users")]
like image 44
Imagin8 Avatar answered Nov 14 '22 21:11

Imagin8


The thing that worked for us was setting these things in Startup.cs (it's cookie based authentication):

public void Configure(...)
{
    (...)
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
        AutomaticAuthenticate = true,
        CookieHttpOnly = true,
        CookieSecure = CookieSecurePolicy.SameAsRequest
    });

    app.UseOpenIdConnectAuthentication(CreateOpenIdConnectOptions(_customConfig));
    (...)
}

And setting up the options:

private OpenIdConnectOptions CreateOpenIdConnectOptions(CustomConfigurationFile configuration)
{
    var options = new OpenIdConnectOptions
    {
        AuthenticationScheme = "oidc",
        SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
        Authority = configuration.ServerAddress + "/auth/realms/" + configuration.Realm,
        RequireHttpsMetadata = true,
        PostLogoutRedirectUri = configuration.SystemAddress,
        ClientId = configuration.ClientId,
        ClientSecret = configuration.ClientSecret,
        ResponseType = OpenIdConnectResponseType.Code,
        GetClaimsFromUserInfoEndpoint = true,
        SaveTokens = true
    };
    options.Scope.Clear();
    options.Scope.Add("openid");
    return options;
}
like image 1
mikes Avatar answered Nov 14 '22 20:11

mikes