OIDC with custom UserModel

Hi,
I’m trying a quick POC with Keycloak.
This includes Keycloak 8.0.1 and a tiny Spring Boot Client App.
I have setup the system, so that native keycloak users can login via OIDC to the client app.
Further I followed this doc
https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi
to integrate external custom users. These users can login, but OIDC fails.
This is what Keycloak logs:

18:31:38,497 WARN  [org.keycloak.events] (default task-3) type=CODE_TO_TOKEN_ERROR, realmId=torment, clientId=test-client, userId=f:ff4c66e5-2a6f-465c-8418-200648a49973:dfb_user, ipAddress=127.0.0.1, error=not_allowed, grant_type=authorization_code, code_id=bf509e42-135e-4fc1-8559-de87c8ad2c28, client_auth_method=client-secret

The client error is:

[invalid_scope] Client no longer has requested consent from user

I assume my UserModel is to bare-bones, to create an id_token from it.
I couldn’t really find info on what to implement to make it work though.

tldr; What do I have to implement to do to make a custom User Storage SPI work for OIDC?

I have an Angular PWA that enables users to login to Keycloak using Authorization Code Flow with PKCE. It works with Keycloak (native) users and with Federated users.

As per the Keycloak docs: “The built-in LDAP and ActiveDirectory support is an implementation of this (User Storage) SPI in action.”

See:

Sorry, the links you provided don’t really help me.
I don’t have LDAP or AD, but something proprietary. That’s why I already implemented my own User Storage SPI.
I’m sure the provided implementations work.
I have read the documentation for User Storage SPIs, but it doesn’t contain what I need.
Here is what my UserModel implementation looks like:

public class DfbUser extends AbstractUserAdapterFederatedStorage {

	private String username;

	public DfbUser(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel, DfbUserDto userDto) {
		super(session, realm, storageProviderModel);

		this.username = userDto.getUsername();
	}

	@Override
	public String getUsername() {
		return username;
	}

	// TODO Only implemented to avoid this problem
	// https://issues.redhat.com/browse/KEYCLOAK-6115
	@Override
	public void setSingleAttribute(String name, String value) {
		if (!name.equals(LOCALE)) {
	        super.setSingleAttribute(name, value);
	    }
	}

	...
}

I can tell that a bunch of methods on the UserModel are called during the the OIDC process, like:
getFirstName, getFirstAttribute, getFederationLink or getServiceAccountClientLink.
While I can provide the first name through the DTO, I don’t know what some of the other attributes even are, or if they are necessary. Can a token be genertaed with null values for attributes? I just know that I’m missing something.

What I’m trying to find out is what values I have to provide to make it work.

You can refer to the OpenID Connect (OIDC) RFC if you want to learn more about scopes and claims (attributes). You can look at the source code for the built-in LDAP and/or ActiveDirectory SPI’s.

You can look at the tokens returned by the different OIDC AuthN flows: Implicit Flow vs Authorisation Code Flow.

Authorization Code Flow

The Authorization Code Flow is a browser-based protocol that makes heavy use of browser redirects to obtain an identity token and an access token.

Requesting authorization

The user should be sent to a URL to authenticate:

http://localhost:10001/auth/realms/development/protocol/openid-connect/auth?response_type=code&client_id=serendipity-pwa&redirect_uri=http://localhost:4200/authorization-code/callback

For example:

development-realm-login

Example response:

http://localhost:4200/authorization-code/callback?session_state=04c1e2e4-e6a2-460f-841a-83ef532b01d8
&code=3bffbda8-1453-4e2c-8edb-bffd096c40b4.04c1e2e4-e6a2-460f-841a-83ef532b01d8.e3e6990e-7c61-48a3-93e0-ee1bbc3724a4
Requesting an access token

To request an access token, use the following curl command (where code is the authorization code you received when you requested authorization):

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=3bffbda8-1453-4e2c-8edb-bffd096c40b4.04c1e2e4-e6a2-460f-841a-83ef532b01d8.e3e6990e-7c61-48a3-93e0-ee1bbc3724a4&redirect_uri=http://localhost:4200/authorization-code/callback&client_id=serendipity-pwa&client_secret=Password12" http://localhost:10001/auth/realms/development/protocol/openid-connect/token

Example response:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU ...",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSld ...",
    "token_type": "bearer",
    "not-before-policy": 0,
    "session_state": "04c1e2e4-e6a2-460f-841a-83ef532b01d8",
    "scope": "email profile"
}

Decode the access_token:

See: Getting started with Keycloak

I do understand how OIDC works.
The user DTOs are actually provided by our existing auth provider, which we are trying to “migrate”.
I just don’t get how this translates to keycloaks data model yet. Stuff like, claims being stored as attributes, for example. And does that mean I have to set an attribute family_name or can keycloak get that from the Users getLastname method - stuff like that.
It is dawning on me that I won’t get any quick wins here :frowning:
I will have a look at the LDAPStorageProvider and try to go from there…

Take a look at keycloak-user-migration/UserModelFactory.java at master · daniel-frak/keycloak-user-migration · GitHub about how to populate the UserModel with data coming from an external source.