FortiAuthenticator as Identity Provider.

259 views
Skip to first unread message

Miquel Comas

unread,
Aug 20, 2025, 11:32:16 AMAug 20
to Keycloak User
Hi, I am trying to add FortiAuthenticator (6.6) as OpenID Connect v1.0 to Keycloak 26.3.2, but doesn’t work.

The problem arise when KC call FortiAuthenticator token endpoint (/api/v1/oauth/token/) witch return always the same error message: "Sorry, this request could not be processed. Please try again later."

I’m pretty sure the clientID and clientSecret are correct. Just debugging I saw the http request:
POST /api/v1/oauth/token/
HTTP/1.1..
Content-Length:293 
Content-Type:application/x-www-form-urlencoded; charset UTF-8 
Host: aaa.bb.com 
Connection: Keep-Alive 
User-Agent: Apache-HttpClient/4.5.14(Java/21.0.8)
Accept-Encoding:gzipdeflate 
code=TkBYC....snFyB & 
grant_type=authorization_code & 
redirect_uri=https%3A%2F%2Fjjj.stage.gggg.com%2Frealms%2Fadmit-users%2Fbroker%2Fcsapg%2Fendpoint &
client_secret=aM0W....yqI & 
client_id=AX0b....5cJy5jo 

I can’t see any error at this request, but checking FortiAuthenticator documentation (https://docs.fortinet.com/document/fortiauthenticator/6.6.6/rest-api-solution-guide/498666/oauth-server-token-oauth-token) seems that FortiAuthenticator expect 'Content-Type: application/json' Instead the Content-Type:application/x-www-form-urlencoded; charset UTF-8 send by my KC.

Is there a method to force KC to send token request with Json Content-type instead of x-www-form-urlencoded ? Does anyone have any other ideas as to what the problem with FortiAuthenticator oauth/openid might be?

Br,
M

> Este mensaje va dirigido a su destinatario, de manera exclusiva, y puede contener información confidencial y sujeta al secreto profesional, la divulgación del cual no está permitida. En caso de haber recibido este mensaje por error, le rogamos que, de manera inmediata, nos lo comunique mediante correo electrónico remitido a nuestra atención y proceda a su eliminación, así como a la de cualquier documento adjunto a este. Por tanto, le comunicamos que la distribución, copia o utilización de este mensaje, o de cualquier documento adjunto a este, cualquiera que fuera su finalidad, están prohibidas por la ley.
> En cumplimiento del Reglamento (UE) 2016/679 del Parlamento Europeo y del Consejo, de 27 de abril de 2016, puede ejercer los derechos de acceso, rectificación, cancelación, limitación, oposición y portabilidad de manera gratuita mediante correo electrónico a protecci...@qida.es.

Thomas Darimont

unread,
Aug 20, 2025, 3:12:50 PMAug 20
to Keycloak User

Hello Miquel,

fortiauthenticator is indeed not following the oauth 2 spec (using OAuth 2.1 as a reference https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-2.4.1) which IMHO allows 
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.

Kind regards,
Thomas

Miquel Comas

unread,
Aug 21, 2025, 3:31:07 AMAug 21
to Keycloak User
Thanks a lot, Thomas, but since I can't guarantee that sending the request as JSON will work, I don't know if it's worth the effort.
I'm thinking about using SAML or creating a ticket to Fortinet to see if they can follow the standard.

Br,
M

Message has been deleted

Miquel Comas

unread,
Aug 21, 2025, 10:47:46 AMAug 21
to Keycloak User
I found the problem. 

I tested with curl and I found that if i send "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" the FortiAuthenticator fails, to solve I only need to change to  "Content-Type: application/x-www-form-urlencoded".
Do I need to create a custom IdentityProvider to change this ? Is there other method ?

Br,
M



Alexander Schwartz

unread,
Aug 25, 2025, 6:51:53 AMAug 25
to Miquel Comas, Keycloak User
Hi Miquel,

Please raise a GitHub issue as a bug so we can look into it more. Looking at the spec, the UTF-8 seems to be optional and the assumed default. Also the OIDC standard doesn't have the encoding added in its spec. 
Maybe we would just remove this from the implementation, but we'll need to see what the discussion brings. 


--
You received this message because you are subscribed to the Google Groups "Keycloak User" group.
To unsubscribe from this group and stop receiving emails from it, send an email to keycloak-use...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/keycloak-user/da72fa65-07e7-4455-8311-edc674830efen%40googlegroups.com.


--

Alexander Schwartz, RHCE

He/Him

Principal Software Engineer, Keycloak Maintainer

alexander...@ibm.com


IBM Data Privacy Statement 


IBM Deutschland Research & Development GmbH

Vorsitzender des Aufsichtsrats: Wolfgang Wendt

Geschäftsführung: David Faller

Sitz der Gesellschaft: Böblingen / Registergericht: Amtsgericht Stuttgart, HRB 243294

Miquel Comas

unread,
Aug 25, 2025, 6:57:43 AMAug 25
to Keycloak User
Many thanks Alexander, I already did it: https://github.com/keycloak/keycloak/issues/42063

Alexander Schwartz

unread,
Aug 25, 2025, 7:03:23 AMAug 25
to Miquel Comas, Keycloak User
Hi Miquel,

I see that this was opened as an enhancement request, and due to that it is missing a lot of information that we ask for in the bug template. 

Please create a new issue using the bug template, then again post the link to the issue in this thread.

Best,
Alexander

Miquel Comas

unread,
Aug 25, 2025, 7:18:02 AMAug 25
to Keycloak User
Reply all
Reply to author
Forward
0 new messages