Keycloak RBAC for nginx reverse proxy - keycloak

I started to work with keycloak, and here is a setup I want to test.
I want to test this scenario:
It works, but I want to implement role-based access to apps behind Nginx proxy and I can't understand how exactly payload of jwt token generates.
Here is my JWT tokens payload:
"jti": "f5f07b6f-ccae-4f57-a8ea-ae02ebb3cb12",
"exp": 1569263630,
"nbf": 0,
"iat": 1569227630,
"iss": "https://keycloak.domain.local/auth/realms/LDAP_test",
"sub": "fedc6baf-4ba4-4fa6-924c-9501edf070f7",
"typ": "Serialized-ID",
"auth_time": 0,
"session_state": "aa0052ee-b5e1-45cc-bee4-e7bccdfa4a59",
"state_checker": "sC_nvlDXfjUDHhC15ZDpPauX5JkxhvVtYUOn62PhtV8"
I want my token to contain roles, username and email and i run out of ideas how to put it there. Is client somehow related to the content of the token or keycloak always gives everything he have into it?
Here are my Nginx client settings:
server {
server_name demo-a.domain.local;
location / {
proxy_pass http://10.10.10.168/index.html;
access_by_lua '
local opts = {
redirect_uri_path = "/redirect_uri",
accept_none_alg = true,
discovery = "https:/keycloak.domain.local/auth/realms/LDAP_test/.well-known/openid-configuration",
client_id = "nginx-gateway",
client_secret = "19fe43bc-4167-4433-816a-eb96da33f9a3",
redirect_uri_scheme = "https",
logout_path = "/logout",
redirect_after_logout_uri = "https://keycloak.domain.local/auth/realms/LDAP_test/protocol/openid-connect/logout?redirect_uri=https://www.nginx-gateway.domain.local/",
redirect_after_logout_with_id_token_hint = false,
session_contents = {id_token=true}
}
-- call introspect for OAuth 2.0 Bearer Access Token validation
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
';
}
Thanks in advance!

Did you configure the client to be openid client? Is it UI or machine client?
To cause your JWT to have roles, one follows usual below flow:
Create client in Keycloak admin-console and configure it to support
open-id-connect.
Add users to Keycloak.
Assign roles to users.
Configure your client to have the client id of Keycloak client.
Exercise browser or machine flow which involves passing of JWT to the
client. JWT will contain the roles as claims.

Still debugging it, but here are a few pointers you will definitely need....
You need to be Authenticating Reverse Proxy with KeyCloak while using the keycloak behind an nginx proxy

Ok, completely forget about this question, but still here is a solution.
Just use a proper oidc proxy like gatekeeper (louketo now) or oauth2-proxy.

Related

Configure remote-user name in Wildfly OIDC

I successfully configured Wildfly 26 to authenticate via Keycloak following the usual tutorial. Example: https://www.youtube.com/watch?v=2gQO4_7Z5CI
It works as announced - when required the user's browser gets redirected to keycloak, after login it redirects back. That much for authentication. I also checked that the authorization can be performed by running request.isUserInRole(...) which is true if and only if in keycloak some role was associated to that user. That much for authorization.
But the methods request.getRemoteUser() and request.getUserPrincipal().getName() only return the keycloak's userid, which looks like 896128e8-3260-42c8-9457-3c8ee236eb4d.
How can I make Wildfly OIDC to report the username by looking at the preferred_username token claim?
So I learned that Wildfly's OIDC client can be configured to read the principal name from another token claim. For this it was sufficient to add the line
"principal-attribute": "preferred_username",
into my web application's WEB-INF/oidc.json. This is the only line required on top of the configuration generated by Keycloak:
{
"principal-attribute": "preferred_username",
"realm" : "myrealm",
"auth-server-url" : "http://localhost:8180/",
"ssl-required" : "external",
"resource" : "webapp",
"credentials" : {
"secret" : "7BqGXjt7cw6p88DNoiXCi1663bIzKPwY"
},
"confidential-port" : 0
}
When authenticating, the tokens emitted by Keycloak are exactly the same however the client uses the specified field's value instead of the sub value.
From the documentation:
principal-attribute
OpenID Connect ID Token attribute to populate the UserPrincipal name with. If token attribute is null, defaults to sub. Possible values are sub, preferred_username, email, name, nickname, given_name, family_name.

Register confidential OIDC client through registration endpoint

I would like to programmatically register confidential OIDC client (the client is a backend service). I checked the keycloak document about it. And I use the “Initial access token“ approach as recommended.
After I created a "initial access token" on the UI console, I register a new client by:
POST https://my-keycloak-host/auth/realms/MyRealm/clients-registrations/default
Headers: Authorization: Bearer <initial access token>
{ "clientId": "my-client" }
The response contains a registrationAccessToken. But I expect to get a client secret. How can I get it? And what is the usage for that registrationAccessToken?
For some unknown reason Keycloak doesn't set secret properly. But you can define own secret with secret property in the payload (tested with Keycloak 16.1.1), e.g.:
{
"clientId": "my-client",
"secret": "my-secret"
}
Doc:
https://openid.net/specs/openid-connect-registration-1_0.html
https://www.keycloak.org/docs/latest/securing_apps/index.html#_client_registration

Keycloak frontend and backend clients

This is related to keycloak clients. My frontend is connected to public client and backend is connected to confidential client.
I am able to login, get the code, as I am using response_type=code by turning on "Standard Flow Enabled".
This code redirects and returns me Idtoken, refreshtoken and token.
Now I need to communicate with backend which is confidential, I would like to authenticate user using some of the values which I have received from the frontend client.
How can I do that?
Here is my frontend and backend conf
FRONTEND
{
"realm": "xyz",
"auth-server-url": "http://localhost:8333/auth/",
"ssl-required": "external",
"resource": "frontend-app",
"public-client": true,
"confidential-port": 0,
"enable-cors": true
}
BACKEND
keycloak.auth-server-url=http://localhost:8333/auth
keycloak.realm=xyz
keycloak.resource=backend-app
keycloak.principal-attribute=preferred_username
keycloak.bearer-only=true
keycloak.credentials.secret=xxx-xxx-xxx
this is from realm setting
This might help somebody.
My backend service which is springboot project with spring security keycloakAuthenticationProvider does authenticate the token received from the frontend public client.
Call from frontend
axios({
method: 'GET',
url: '/api/authenticate',
headers:{'Authorization': 'Bearer '+keycloak.token+''}
}).then((r) => {
console.log("response",r)
})
Call to backend
#GetMapping("/api/authenticate")
public ResponseEntity<SecureUserDto> authenticate() {
String username = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
User user = userRepository.findWithPrivilegesByUsername(username);
return ResponseEntity.ok();
}
But i still was not able to get it right on postman at ../token end point provided by keycloak server.
Anyways my work is done.

How to add policy to Keycloak - UI crashes

I'm trying to enable flow when some admin user by some admin client is able to create users and obtain their access tokens to be used for another clients.
I have KeyCloak setup with token exchange and fine grained authz enabled and configured clients. I'm able to login my admin user by REST api, then exchange token. But when I specify audience I got error.
This one returns token but I need token for another client/audience.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs......
This one is failing with error.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs...... audience=my-another-client
{
"error": "access_denied",
"error_description": "Client not allowed to exchange"
}
So I tried to setup fine grained auth for target audience client (enabled it in tab, then tried to add policy for my admin user to be able to exchange token) but when I want to add policy that will allow my admin user to perform token exchange I'm stuck on UI error.
When typing policy name I got 404 when Keycloak is looking for name colisions. Afaik 404 in this case shouldn't block form from posting because it is no name collision. Instead I got instantly redirected with error.
https://my-keycloak-server.com/auth/admin/realms/my-realm/clients/1bafa9a4-f7e2-422c-9188-58ea95db32ef/authz/resource-server/policy/search?name=some-name
In the end of the day I can't add any policy in Keycloak. All the time form validation is ending up with crash caused by 404 policy name not found.
I'm using dockerized keycloak 10.0.0
Any ideas?
I hacked it by live editing Angular JS UI script function that performs verification in line 2403.
this.checkNameAvailability = function (onSuccess) {
if (!$scope.policy.name || $scope.policy.name.trim().length == 0) {
return;
}
ResourceServerPolicy.search({
realm: $route.current.params.realm,
client: client.id,
name: $scope.policy.name
}, function(data) {
if (data && data.id && data.id != $scope.policy.id) {
Notifications.error("Name already in use by another policy or permission, please choose another one.");
} else {
onSuccess();
}
});
}
to
this.checkNameAvailability = function (onSuccess) {
onSuccess();
}
And that end up with successfuly added policy. Still looks like it's UI bug.

invalid Client for exchange code _ identityserver3 _ AppAuth

I'm using AppAuth framework for authentication in my Swift app, I can login but after login and coming back to my app, I can't exchange the code and get error invalid client.
My client configuration is:
var client = new Client
{
ClientId = "IOS.Client",
ClientName = "IOS Client",
RedirectUris = { "com.mysite.accounts:/oauthredirect" },
AllowedGrantTypes = GrantTypes.Code, //AuthorizationCode
AllowedScopes = { "openid", "profile", "offline_access" },
};
I set ClientSecret (SHA-256) and my ClientId is ok but I get error invalid client.
I checked my logs and there is an error: secret validators could not validate secret
what's wrong?
IIRC in IS3 you had to set a client secret. We didn't allow empty ones.
Either you set a secret on client and server, or upgrade to IS4 - IS3 is deprecated since quite some time.
When using IdentityServer3 and AppAuth you should not use special characters in ClientSecret and set your grant AuthorizationCodeWithProofKey.
this link is useful