Sunday, November 4. 2018
MS-SFU kerberos extensions and cross-realm support

The last month I have been working in a enhancement to make SFU kerberos extensions in Java to work in multi-realm environments. The MS-SFU (Microsoft Service for User) are two kerberos extensions created by Microsoft to provide delegation and impersonation features to the kerberos standard. Delegation is managed by the S4U2proxy extension, a server can request a kerberos ticket on behalf of a user to access a subsequent server. For that with the server's own ticket and the user's ticket the server requests to the KDC (Kerberos Domain Controller) another ticket to connect to a backend server. The ticket is issued and the access to the backend server is performed as it was done by the initial user that connected to the intermediate server. The impersonation moves delegation one step forward, performed by the S4U2self extension, the frontend server in this case is a legacy server (as Microsoft calls it) which does not support kerberos, therefore there is no ticket for the user (the final user logged in using another method, for example common username and password), and this intermediate server performs a full impersonation with its own ticket to access the final backend on behalf of the user. Obviously this features should be configured, the frontend server should be granted with rights to do both operations, and Microsoft currently gives several ways of granting those privileges.
In general the SFU extensions are old and supported by Java since Java 8 but with windows 2012R2 Microsoft added support to cross-realm access. Previously the three roles (final user, frontend server and backend server) should be in the same realm/domain but a new concept, called resource-based constrained delegation, was incorporated to expanse SFU for multi-realm. The JDK never supported the new feature as stated in this JDK bug.
In theory the documentation for MS-SFU is available in the first link at the beginning of this entry but, in my humble opinion, the explanation of how to call both extensions leaves much to be desired. To my surprise, after a lot of tests and tries, when my two demos (delegation and impersonation) started to work, the procedure was not so difficult as it seems to be. Therefore I decided to write how cross-realm for SFU works for me in windows, in an easier way than official documentation.
S4U2self:
In the self (impersonation) extension the idea is simple, there are two roles, the user to impersonate and the server that impersonates. First the server should know in which realm the user is located (that part is out of the scope of this explanation). Once you know the realm where the user resides, you get a TGT (ticket) to that realm and perform a S4U2self against the KDC of the user's realm. The KDC returns a proper ticket with the information for that user (PAC extensions with roles and everything is included in the credentials) because, in the end, this is the KDC for the user, the one that knows the account information. After that, the ticket received is updated back to the server realm and a final S4U2self is executed against the local realm. This time the user information is there and the impersonation in the local realm can be completed. So, in summary, two S4U2self calls are needed, first in the user's realm (this one obtains the user information) and then another one in the server's realm. Moving from one realm to another requires to obtain TGTs and jumping from realm to realm following authentication paths (capaths).
S4U2proxy:
In the proxy call (delegation) the idea is similar, but not the same, because now we have a third role, the backend service we are delegating the user to, and two tickets, the user's ticket and our own server's ticket. In this case I finally made it work doing the following steps. First a S4U2proxy is executed against the local server realm, this way we obtain the user's service ticket we can move to the backend's realm. Then both credentials can be moved realm by realm until we reach where the backend server is located. We have to move both tickets (user's and server's service ticket). There a new S4U2proxy is executed that finally returns all the proper grants to call the backend server.
For a complete view, the needed configuration to the involved accounts should be described. In this case we need to setup the resource-based constraints delegation using power-shell commands. For the delegation/S4U2proxy the frontend account should be obtained from the domain and assigned as a valid user to be delegated to the backend server (in this example the accounts are called frontend and backend respectively):
$frontend = Get-ADUser -Identity frontend -server FRONTEND.DOMAIN.COM
Set-ADUser -Identity backend -server BACKEND.DOMAIN.COM -PrincipalsAllowedToDelegateToAccount $frontend
Get-ADUser -Identity backend -server BACKEND.DOMAIN.COM -Properties PrincipalsAllowedToDelegateToAccount
The commands use the server option to read the accounts from the needed realm, this way the accounts can be in different AD domains.
In order to perform impersonation the frontend user should be allowed to do so:
Set-ADAccountControl $frontend -TrustedToAuthForDelegation $true
This sets up the delegation for any authenticated protocol in the frontend user, the associated user in AD presents the following configuration.

It seems that S4U2proxy uses the PrincipalsAllowedToDelegateToAccount, users from different domains can be configured as allowed to delegate to the backend account. But in the case of S4U2self the user should have the account control that allows the account to impersonate, TrustedToAuthForDelegation (which is shown as use any authenticated protocol in the user delegation tab).
And that is all. Today's entry is just the explanation of how I think SFU extensions work when different realms are involved. We are trying to add cross-realm to SFU and support for resource-based constraint delegations inside the JDK implementation, my demos are working now but I suppose the process is going to be long so I prefer to, for the moment, not anticipate any event in the blog. I will add more details if I see something moves forward.
Best regards!
Saturday, September 15. 2018
Using custom URLs to perform SSO in an android application


Today's entry presents a keycloak integration for an android application. Keycloak is an open-source project that offers Single Sign-On and access management for modern applications and services, and it has been used before in the blog. It is based on standards (like OpenID Connect and SAML) and provides some adapters for different programming languages and application platforms. Previously any integration between a mobile application and keycloak used an embedded browser to perform the login, but now that solution is a no-go. Google started to avoid this technique and recommend custom URLs long time ago. I have almost zero experience in mobile development (sorry for that) but I wanted to test this solution by myself anyway.
The idea is simple, as a mobile program is a client-side application I decided to use a public profile (similar situation to the one in browser-side JavaScript). I think that having a password in the client is almost the same than using none, it gives no extra security. The application is going to be a java one (it is just a PoC, so using kotlin was an extra effort for me).
First creating the public keycloak client for the application is needed. The application will use a weird URL keycloak://keycloaksample/.

Starting with the android application, the custom URL should be defined in the AndroidManifest.xml, in my case for the .MainActivity.
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="keycloak"
android:host="keycloaksample"
android:pathPrefix="/"/>
</intent-filter>
</activity>
With this configuration the .MainActivity will be called when the browser receives the keycloak://keycloaksample/ URL from the SSO server. That feature will be used to use the external browser to do the login and the logout. So a button will initiate the browser which will go to the authentication URL.
public void login(View view) {
// "http://192.168.100.1:8080/auth/realms/master/protocol/openid-connect/auth?client_id=KeycloakSample&scope=openid&response_type=code&redirect_uri=app%3A%2F%2Fkeycloaksample%2F";
Uri uri = new Uri.Builder().scheme(configuration.getProperty("keycloak.scheme"))
.encodedAuthority(configuration.getProperty("keycloak.authority"))
.appendEncodedPath("auth/realms")
.appendPath(configuration.getProperty("keycloak.realm"))
.appendEncodedPath("protocol/openid-connect/auth")
.appendQueryParameter("client_id", getString(R.string.app_name))
.appendQueryParameter("scope", "openid")
.appendQueryParameter("response_type", "code")
.appendQueryParameter("redirect_uri", configuration.getProperty("app.url"))
.build();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
If you see, the URL is created with the application URL as the redirect_uri. The idea is the login button starts the default browser with the keycloak authentication URL (android starts the app associated to an http(s) scheme), the user performs the login with it, and after that the server redirects the browser to the application URL, which starts the activity inside our app.
Now it's the time to follow the OIDC specification an get the code returned from the server and, with it, obtain the access and refresh token. In the onCreate of the activity a LoginTask starts a thread that executes the call to the token endpoint.
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
// check if we have to login
Uri uri = intent.getData();
if (uri != null) {
String code = uri.getQueryParameter("code");
if (code != null) {
new LoginTask(this,
new Callback() {
@Override
public void onPostExecute(MainActivity activity, TokenResponse tokenResponse) {
activity.setToken(tokenResponse);
}
}
).execute(code);
}
}
}
The LoginTask performs the authorization_code call to convert the code into the real tokens (remember this is just plain OIDC standard).
@Override
protected TokenResponse doInBackground(String... strings) {
HttpURLConnection conn = null;
try {
String code = strings[0];
URL keycloak = activity.createTokenURL();
String urlParameters = activity.createAuthorizationCodePostData(code);
byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
conn = (HttpURLConnection) keycloak.openConnection();
conn.setDoOutput(true);
conn.setInstanceFollowRedirects(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "utf-8");
conn.setRequestProperty("Content-Length", Integer.toString(postData.length));
conn.setUseCaches(false);
try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) {
wr.write(postData);
}
if (conn.getResponseCode() == 200) {
return new TokenResponse(conn.getInputStream(), activity.getSignatureKey());
} else {
return new TokenResponse(conn.getResponseCode() + ": " + conn.getResponseMessage());
}
} catch (IOException|InvalidKeySpecException|NoSuchAlgorithmException e) {
Log.d(TAG, "Error calling keycloak", e);
return new TokenResponse(e.getMessage());
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
The createTokenURL and createAuthorizationCodePostData methods assign the URL and the post data that will be sent to the server to obtain the tokens.
public URL createTokenURL() throws MalformedURLException {
// "http://192.168.100.1:8080/auth/realms/master/protocol/openid-connect/token"
return new URL(new Uri.Builder().scheme(configuration.getProperty("keycloak.scheme"))
.encodedAuthority(configuration.getProperty("keycloak.authority"))
.appendEncodedPath("auth/realms")
.appendPath(configuration.getProperty("keycloak.realm"))
.appendEncodedPath("protocol/openid-connect/token")
.build().toString());
}
public String createAuthorizationCodePostData(String code) {
// "grant_type=authorization_code&client_id=KeycloakSample&redirect_uri=app%3A%2F%2Fkeycloaksample%2F&code=" + code
return new Uri.Builder()
.appendQueryParameter("grant_type", "authorization_code")
.appendQueryParameter("client_id", getString(R.string.app_name))
.appendQueryParameter("redirect_uri", configuration.getProperty("app.url"))
.appendQueryParameter("code", code)
.build().getEncodedQuery();
}
And finally, with the JSON data returned by the server, a TokenResponse object is created. For that I used the jjwt project which can parse the different JWT tokens returned by the server. This way the application can obtain the token information and present the user info.
public TokenResponse(InputStream is, Key key) throws IOException {
this.creationTime = System.currentTimeMillis() / 1000L;
this.key = key;
JsonReader jsonReader = new JsonReader(new InputStreamReader(is, "UTF-8"));
jsonReader.beginObject();
while(jsonReader.hasNext()) {
String name = jsonReader.nextName();
switch (name) {
case "expires_in":
this.setExpiresIn(jsonReader.nextInt());
break;
case "refresh_expires_in":
this.setRefreshExpiresIn(jsonReader.nextInt());
break;
case "access_token":
this.setAccessToken(jsonReader.nextString());
break;
case "refresh_token":
this.setRefreshToken(jsonReader.nextString());
break;
default:
jsonReader.skipValue();
}
}
}
public Jws getAccessTokenClaims() {
return accessTokenClaims;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
this.accessTokenClaims = Jwts.parser()
.setSigningKey(this.key)
.parseClaimsJws(accessToken);
}
To fully understand the code presented, the properties file used in the app is the following (information about keycloak: url, realm key,...).
keycloak.key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8...
keycloak.scheme=http
keycloak.authority=192.168.100.1:8080
keycloak.realm=master
app.url=keycloak://keycloaksample/
Mainly this is the idea about the custom URL technique, android can associate a weird URL with the application and normal browser login is used to perform the SSO login. After that the OIDC calls are used to get the tokens and use them. My application also exemplifies how to do the logout, refresh the token and call another web service (userinfo) using the access token. But that is just development that uses the TokenResponse object initially created in the login.
Here I present a video, first I log into the application, thanks to custom URLs the browser is used to do that. Then the token information is shown and it is used to call the OIDC userinfo endpoint (information of the user). As you see the SSO is in place and I can reach the keycloak console without login again. Then I wait the access token to expire (60 seconds) and I refresh it (refresh_token action in the OIDC token endpoint). Finally I perform the logout which is a global logout too.
And that is all. I wanted to test the custom URL technique in an mobile application and I decided to use android (iOS is a complete unknown for me, but I suppose the same idea can be used for that platform). The application is very simple and just uses the jjwt project out of the default android API. This is just a PoC so it can be extended and/or improved a lot (for example not using fixed URLs and keys, calling the well-known OIDC address and the JWKs certificate endpoints is a better solution). Please also take in mind that I never develop for mobile, so just use the app as an example, surely things can be done much better. The KeycloakSample application can be download from here, hope it helps to someone else.
Regards!
Saturday, August 4. 2018
Authenticating a JWT token in wildfly


Sometimes I get surprised with the lack of information and/or documentation around wildfly and JBoss. If you search over the security documentation for wildfly, it supports several authentication mechanisms, by default BASIC, FORM, SPNEGO and CLIENT-CERT. There is no mention to bearer tokens or JWT but, surprise, surprise, the support is there and it can be used out of the box. There is very scarce documentation but you can find some examples and a quickstart about this feature.
A JSON Web Token (JWT) is an access token than contains three parts: a header, a payload and a signature. The header is just a JSON with some fixed fields (like type -jwt in this case-, algorithm of the signature or the id of the key used). The payload is the interesting part and is just any JSON with the information that the token is going to interchange, it can be any information, but things like username, roles, groups, name and surname are common. Among those fields there are two typical keys: iss or issuer and aud or audience. The issuer is who is issuing the token. The audience is for what endpoint the token was created. Wildfly implementation can be configured to check these two fields. Finally a signature of the header and payload is added to ensure that the token is valid and has not been modified (non-repudiation and integrity). In current days these tokens are ubiquitous to authenticate and authorize web services applications.
I had no idea about this feature (even more, I thought that there was no bearer authentication by default in wildfly) and I had recommended custom developments several times for this use-case. That was a wrong advise or, at least, an incomplete one. So, once I have heard about this mechanism I thouhgt that it was fair to test it and know more about how it works. This entry is going to test the new BEARER_TOKEN mechanism introduced by elytron in a wildfly 13. The JWT token is going to be provided by the keycloak project but the server is going to consume it just using elytron (no agent provided by keycloak is going to be used, just plain wildfly BEARER_TOKEN mechanism).
The steps to setup the demo are the following.
First download and install wildfly 13.
unzip wildfly-13.0.0.Final.zip cd wildfly-13.0.0.Final/bin/ ./add-user.sh -u admin -p XXXXX ./standalone.sh
Configure with offset 10000 (keycloak will be running at default ports):
./jboss-cli.sh --connect /socket-binding-group=standard-sockets:write-attribute(name=port-offset, value="${jboss.socket.binding.port-offset:10000}") reload
Now just do the same for keycloak.
unzip keycloak-4.1.0.Final.zip cd keycloak-4.1.0.Final/bin/ ./add-user.sh -u admin -p XXXXX ./add-user-keycloak.sh -r master -u admin -p XXXXX ./standalone.sh
Using the keycloak administration several objects are created. First a role that will be assigned to our users. In Roles click Add Role and create a user role.
Then create the user to be used with that role. Go to Users and Add user.
Assign a password in the Credentials tab (non-temporary) and finally assign the user role to it.
In Clients click Create and fill the wildlfy client information. This client will be used to get the token for the application (using direct login or direct grants) and with that token the wildfly endpoint will be called.
When you configure a client as confidential in the second tab, Credentials, you can set a secret for the client. This password will be needed for some operations later on (for login using client credentials and to configure introspection).
In the Scope tab full scope is removed and only the role user is assigned. This way the JWT token is restricted to contain only this role (if the user has it) and not all the user information.
Finally a mapper is going to be configured. This part is the only tricky point of the demo. The wildfly authenticator expects the roles in a top-level field in the JWT token, but keycloak uses the claim realm_access.roles (two levels). For that reason the role list is going to be moved to a Roles field (in elytron this is the default attribute is for roles, although any another attribute can be configured using a role-decoder). Create protocol mapper in the Mappers tab.
Now that keycloak is configured, we need the certificate that is going to be used to sign the token. We can get it from keycloak in Realm Settings → Keys → RSA type → Click Certificate. Create a keystore with it. The base64 data should be enclosed with -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines to be in a PEM format (the file keycloak.pem was generated).
cd ${WILDFLY_HOME}/standalone/configuration keytool -importcert -keystore keycloak.jks -storepass changeit -file keycloak.pem -alias keycloak -trustcacerts
Going back to wildfly, elytron should be configured now to create a new domain using a token-realm. In a first step the realm is going to be configured to check a jwt token.
/subsystem=elytron/key-store=jwt-key-store:add(type="JKS", relative-to=jboss.server.config.dir, path="keycloak.jks", credential-reference={clear-text="changeit"}) /subsystem=elytron/token-realm=jwt-realm:add(jwt={issuer=["http://localhost:8080/auth/realms/master"], audience=["wildfly"], key-store=jwt-key-store, certificate="keycloak"}, principal-claim="preferred_username") /subsystem=elytron/security-domain=jwt-domain:add(realms=[{realm=jwt-realm}], permission-mapper=default-permission-mapper, default-realm=jwt-realm) /subsystem=elytron/http-authentication-factory=jwt-http-authentication:add(security-domain=jwt-domain, http-server-mechanism-factory=global, mechanism-configurations=[{mechanism-name="BEARER_TOKEN", mechanism-realm-configurations=[{realm-name="jwt-realm"}]}]) /subsystem=undertow/application-security-domain=jwt-domain:add(http-authentication-factory=jwt-http-authentication)
As commented before the jwt is configured to only admit access tokens that are emitted by a set of issuers and to a specific audience (it seems those two fields are optional and can be omitted). The key-store is used to reference the certificate that keycloak is going to use to sign the tokens. This way elytron will validate that the signature is valid, being sure that the token was generated by keycloak and not modified by someone else. The attribute to identify the user will be preferred_username and nothing is specified for roles, so the default Roles attribute will be used.
Finally some debug levels are configured to check that everything is working.
/subsystem=logging/logger=org.wildfly.security:add(level=ALL) /subsystem=logging/logger=org.wildfly.elytron:add(level=ALL)
A hello-world application is developed to use jaxrs and return the name of the logged user. The endpoint is as simple as this one.
package es.rickyepoderi.sample; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @Path("hello") public class HelloWorld { @Context private HttpServletRequest request; @GET @Produces(MediaType.TEXT_PLAIN) public String world() { return "Hello " + (request.getRemoteUser() != null? request.getRemoteUser() : "world") + "!!!"; } }
In order to use the previous configuration the web.xml should use a security-constraint and the authentication method should be set to BEARER_TOKEN. Besides the user role is configured to give access to the application (this way the application is ensuring that token roles are parsed correctly).
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <security-constraint> <web-resource-collection> <web-resource-name>Protect all application</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BEARER_TOKEN>/auth-method> <realm-name>jwt-domain</realm-name> </login-config> <security-role> <description>Role required to log in to the Application>/description> <role-name>user</role-name> </security-role> <session-config> <session-timeout>30</session-timeout> </session-config> </web-app>
In the jboss-web.xml just the domain added to undertow is configured.
<?xml version="1.0" encoding="UTF-8"?> <jboss-web> <security-domain>jwt-domain</security-domain> </jboss-web>
Now a little test.sh is created in order to test the endpoint (you can find it inside the maven project). A curl command is executed to login and get a token using direct grants over the keycloak client. That token is sent inside the Authorization header to call the wildfly endpoint. The script is very simple but it works.
bash test.sh Hello ricky!!!
The hello endpoint is returning my username so the token is being correctly validated and the username and roles are obtained.
Now let's try the introspection. The introspect is an endpoint that an OAuth 2.0 server gives to validate access tokens. This way, instead of using wildfly internal code, the container will call this endpoint to validate the access token. This has some advantages, for example, that the token is not restricted to be a JWT one.
So now the configuration of the jwt-realm should be modified from jwt to oauth2-introspect. The elytron configuration just offers to use a username/password configuration using BASIC authentication, but, as I commented previously, this is the default authentication for a confidential keycloak client, so it should work. The username is the client name, wildfly, and the password the one generated in the Credentials tab when the client was configured.
/subsystem=elytron/token-realm=jwt-realm:undefine-attribute(name=jwt) /subsystem=elytron/token-realm=jwt-realm:write-attribute(name=oauth2-introspection, value={introspection-url=http://localhost:8080/auth/realms/master/protocol/openid-connect/token/introspect, client-id=wildfly, client-secret=39917c0f-139a-42d2-af03-2b4207758242})
And my little script continues returning the same output, therefore the introspection endpoint is validating the ticket as expected.
So, it is working. Wildfly and the new elytron subsystem has a poor documented mechanism called BEARER_TOKEN, which lets you authenticate using JWT tokens (and, developing a custom realm, can be extended to more tokens, in general, to any token sent in the Authorization header with the BEARER prefix). In this entry the keycloak single sign-on product was installed to obtain the JWT token and then a token realm was configured inside elytron to validate it. First an internal validation was done (the wildfly code checks issuer, audience and signature) and then an OAuth introspection endpoint was configured to do the same (the server just calls the endpoint to validate the token). Both configuration work. Remember that the only trick was moving the roles to first level claim (Roles in this case), that is needed because wildfly just considers attributes at first level (and keycloak adds roles by default in a more complex JSON document). After my warning some documentation is starting to come to the project.
Tokenized regards!
Comments