application/json in the Token Response but not for the Token Request.
Unfortunately, the code within AbstractOAuth2IdentityProvider#generateTokenRequest
is not so easy to adapt to the fortiauthenticator without hacks.
I think with a custom extension you will be able to create a new Oidc Identity provider implementation. The example below shows how you can replace
the default OIDCIdentityProvider with a custom implementation that handles the special token request as needed.
Note that I use googles AutoService library to generate the service manifest file.
package com.github.thomasdarimont.keycloak.custom.idp.oidc;
import com.google.auto.service.AutoService;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Map;
/**
* PoC for a custom {@link OidcIdentityProvider} that sends the token requests with content-type application/json.
* To be compatible with
https://docs.fortinet.com/document/fortiauthenticator/6.6.6/rest-api-solution-guide/498666/oauth-server-token-oauth-token */
@JBossLog
public class FortiAuthenticatorIdentityProvider extends OIDCIdentityProvider {
private static final VarHandle simpleHttpParamsHandle;
static {
VarHandle handle = null;
try {
handle = MethodHandles
.privateLookupIn(SimpleHttp.class, MethodHandles.lookup())
.findVarHandle(SimpleHttp.class, "params", Map.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.warn("# Failed to find VarHandle for SimpleHttp params field", e);
}
simpleHttpParamsHandle = handle;
}
public FortiAuthenticatorIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) {
super(session, config);
}
@Override
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
if (getConfig().getAlias().equals("forti-idp")) {
// only apply this for our special forti-idp...
// We need to send the token request as a non-standard JSON object in the body
// See: "Get token (Authorization code)" in
https://docs.fortinet.com/document/fortiauthenticator/6.6.6/rest-api-solution-guide/498666/oauth-server-token-oauth-token return new OIDCEndpoint(callback, realm, event, this) {
@Override
public SimpleHttp generateTokenRequest(String authorizationCode) {
SimpleHttp request = super.generateTokenRequest(authorizationCode);
if (simpleHttpParamsHandle != null) {
// collect the previously set form params
Map<String, String> currentParams = (Map<String, String>) simpleHttpParamsHandle.get(request);
// clear the form params
simpleHttpParamsHandle.set(request, null);
// set the form params as json entity
// this will trigger org.keycloak.broker.provider.util.SimpleHttp.makeRequest to perform a json request
request.json(currentParams);
}
return request;
}
};
}
return super.callback(realm, callback, event);
}
@AutoService(IdentityProviderFactory.class)
public static class Factory extends OIDCIdentityProviderFactory {
@Override
public OIDCIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
return new FortiAuthenticatorIdentityProvider(session, new OIDCIdentityProviderConfig(model));
}
}
}
```
This effectivly converts the previously set form params to a json object and send the proper "non-standard" token request.
For the sake of simlicity I only apply this custom token request handling if the idp alias has a specific name.