Skip to content

Keycloak

Keycloak Notes

  • Keycloak stop support of adapters https://www.keycloak.org/2022/02/adapter-deprecation
  • Best Alternative is https://github.com/panva/node-openid-client, if not using next-auth
Public vs confidential client
  • Public client: Using redirect_uri to authenticate client to server after user login
  • Confidential client: Using client_secret to authenticate client to server, used for backend servers that cannot use user-interaction flow

Docker-compose

Using postgres
# docker stop postgres_kc_03_25_23 keycloak_03_25_23 && docker rm postgres_kc_03_25_23 keycloak_03_25_23 && docker volume rm postgres_data_03_25_23
# docker exec -it postgres_kc_03_25_23 psql -U keycloak_user -d keycloak_DB
# docker exec -it postgres_kc_03_25_23 psql -U postgres

# export POSTGRES_PASSWORD=postgres_password
# docker-compose -f docker-compose-kc.yml up
# docker-compose -f docker-compose-kc.yml down && docker volume rm postgres_data_03_25_23

version: "3.8"
services:
  postgres_kc_03_25_23:
    container_name: postgres_kc_03_25_23
    image: postgres:10.4
    restart: always
    volumes:
      - postgres_data_03_25_23:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloak_DB
      POSTGRES_USER: keycloak_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - "5433:5432"
    networks:
      - chaincue-tech-net

  keycloak_03_25_23:
    container_name: keycloak_03_25_23
    image: quay.io/keycloak/keycloak:19.0.3-legacy
    restart: always
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres_kc_03_25_23
      DB_DATABASE: keycloak_DB
      DB_USER: keycloak_user
      DB_SCHEMA: public
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
      PROXY_ADDRESS_FORWARDING: "true"
    ports:
      - "8080:8080"
      - "8443:8443"
    depends_on:
      - postgres_kc_03_25_23
    networks:
      - chaincue-tech-net

volumes:
  postgres_data_03_25_23:
    name: postgres_data_03_25_23

networks:
  chaincue-tech-net:
    name: chaincue-tech-net
    driver: bridge

Setup Keycloak using nextjs / next-auth

Create [...nextauth].tsx
interface DecodedToken {
  sub: string;
  exp: number;
}

export function decodeJwt(token: string): DecodedToken | null {
  try {
    return jwt.decode(token) as DecodedToken;
  } catch (err) {
    console.error('Error decoding JWT:', err);
    return null;
  }
}

const keycloak = KeycloakProvider({
  clientId: process.env.KEYCLOAK_ID,
  clientSecret: process.env.KEYCLOAK_SECRET,
  issuer: process.env.KEYCLOAK_ISSUER,
});

const refreshAccessToken = async (token: JWT): Promise<JWT> => {
  const myHeaders = new Headers();
  myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

  const urlencoded = new URLSearchParams();
  if (token.refresh_token) {
    urlencoded.append("grant_type", "refresh_token");
    urlencoded.append("client_id", process.env.KEYCLOAK_ID);
    urlencoded.append("refresh_token", token.refresh_token);
    urlencoded.append("client_secret", process.env.KEYCLOAK_SECRET);
  }

  return fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token`, {
    method: 'POST',
    headers: myHeaders,
    body: urlencoded
  })
    .then(response => {
      if (!response.ok) return Promise.reject(new Error('Failed to refresh access token'))
      return response.json()
    })
    .then(result => {
      const decodeToken = decodeJwt(result.access_token);
      if (decodeToken) {
        token.refresh_token = result.refresh_token;
        token.access_token = result.access_token;
        token.id_token = result.id_token;
        token.expires_at = decodeToken.exp
        return token
      } else {
        return Promise.reject(new Error('Failed to decode access token'))
      }
    })
    .catch(error => {
      console.error(error)
      token.error = "RefreshAccessTokenError"
      return token
    });
}

export default NextAuth({
  providers: [keycloak],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    jwt: async ({token, account}) => {
      if (token.expires_at) {
        const expiresAt = new Date(token.expires_at * 1000);
        const currentTime = new Date(Date.now());
        if (currentTime.getTime() < expiresAt.getTime()) {
          return token;
        }
      }
      if (account) {
        token.access_token = account.access_token;
        token.refresh_token = account.refresh_token;
        token.id_token = account.id_token;
        token.providerAccountId = account.providerAccountId;
        token.scope = account.scope;
        token.session_state = account.session_state;
        token.token_type = account.token_type;
        token.type = account.type;
        token.userId = account.userId;
        token.expires_at = account.expires_at;
      }
      return refreshAccessToken(token)
    },
    session: async ({session, token}: { session: Session; token: JWT }) => {
      session.access_token = token.access_token
      session.refresh_token = token.refresh_token
      session.id_token = token.id_token
      session.providerAccountId = token.providerAccountId
      session.scope = token.scope
      session.session_state = token.session_state
      session.token_type = token.token_type
      session.type = token.type
      session.userId = token.userId
      session.expires_at = token.expires_at
      session.error = token.error
      return session
    },
  },
});
Secret needs to be set for production
openssl rand -base64 32
secret: process.env.NEXTAUTH_SECRET
Create types/environment.d.ts].tsx && next-auth.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NEXT_PUBLIC_CLIENT_URL: string
    NEXTAUTH_URL: string
    NEXTAUTH_SECRET: string
    KEYCLOAK_ID: string
    KEYCLOAK_SECRET: string
    KEYCLOAK_ISSUER: string
  }
}


import NextAuth, {DefaultSession} from "next-auth"

declare module "next-auth/jwt" {
  interface JWT {
    accessToken?: string
    idToken?: string
  }
}

declare module "next-auth" {
  interface Session {
    user: {
      address: string
    }
    accessToken?: string
    idToken?: string
  }
}
Configure logout function might vary based on keycloak versions
const handleSignOut = async () => {
    if (data) {
      const post_logout_redirect_uri = process.env.NEXTAUTH_URL;
      const logoutUrl = `https://auth.chaincuet.com/auth/realms/chainbot/protocol/openid-connect/logout?id_token_hint=${data.idToken}&post_logout_redirect_uri=${post_logout_redirect_uri}`;
      signOut().then(() => router.replace(logoutUrl));
    }
};
Configure Login
const {status} = useSession({required: true, onUnauthenticated: () => signIn('keycloak')});
const loading = status === 'loading';
if (loading) return <></>

<SessionProvider session={pageProps.session}>
    <Layout>
        <Component {...pageProps} />
    </Layout>
</SessionProvider>

Theming Keycloak

docker-compose.yml example
#
docker
stop
keycloak_name && docker
rm
keycloak_name && docker
stop
postgres_name && docker
rm
postgres_name && docker
volume
rm
postgres_data
#
docker - compose
up - d

version: '3.5'
services:
    postgres:
        container_name: postgres_name
image: postgres:10.4
volumes:
    -postgres_data
:
/var/
lib / postgresql / data
environment:
    POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: POSTGRES_PASSWORD
ports:
    -"5433:5432"
networks:
    -keycloak_net

keycloak:
    container_name: keycloak_name
image: quay.io / keycloak / keycloak
:
19.0
.3 - legacy
environment:
    DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_DATABASE: keycloak
DB_USER: keycloak
DB_SCHEMA: public
DB_PASSWORD: POSTGRES_PASSWORD
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
PROXY_ADDRESS_FORWARDING: "true"
ports:
    -"8080:8080"
    - "8443:8443"
#
volumes:
    #
-/keycloak-theme:/
opt / jboss / keycloak / themes / keycloak /
depends_on
:
-postgres
networks:
    -keycloak_net

volumes:
    postgres_data:
        name: postgres_data

networks:
    keycloak_net:
        name: keycloak_net
driver: bridge
docker stop keycloak_name && docker rm keycloak_name && \
docker stop postgres_name && docker rm postgres_name && \
docker volume rm postgres_data \
docker-compose up -d
http://0.0.0.0:8080/auth/
http://localhost:8080/auth/
List databases
docker exec -it postgres_name psql -U postgres -c "\l"

Keycloak Extensions

Install an extension
svn export https://github.com/thomasdarimont/keycloak-extension-playground/trunk/auth-identity-first-extension
mvn clean install
docker cp auth-trust-device-0.0.1-SNAPSHOT.jar <container_id>:/opt/jboss/keycloak/standalone/deployments

Using Java webclient

Create new user
    private
static
Mono < String > getAccessTokenFromClientAdminCli()
{
    String
    endpointUrl = "http://localhost:8080/auth/auth/realms/lambda/protocol/openid-connect/token";
    WebClient
    webClient = WebClient.builder().baseUrl(endpointUrl).build();

    MultiValueMap < String, String > body = new LinkedMultiValueMap < > ();
    body.add("username", "manageuser");
    body.add("password", "password");
    body.add("grant_type", "password");
    body.add("client_id", "admin-cli");

    return webClient.post()
        .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        .body(BodyInserters.fromFormData(body))
        .retrieve()
        .bodyToMono(String.class)
        .map(response -> new JSONObject(response).getString("access_token"));
}

private
static
Mono < String > createNewUserUsingAccessToken(String
accessToken
)
{
    String
    endpointUrl = "http://localhost:8080/auth/admin/realms/lambda/users";
    WebClient
    webClient = WebClient.builder().baseUrl(endpointUrl).build();

    JSONObject
    newUser = new JSONObject();
    newUser.put("enabled", true);
    newUser.put("username", "test@gmail.com");
    newUser.put("email", "test@gmail.com");
    newUser.put("firstName", "test@gmail.com");
    newUser.put("lastName", "test@gmail.com");

    JSONArray
    credentials = new JSONArray();
    JSONObject
    credential = new JSONObject();
    credential.put("type", "password");
    credential.put("value", "123");
    credential.put("temporary", false);
    credentials.put(credential);
    newUser.put("credentials", credentials);

    return webClient.post()
        .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
        .contentType(MediaType.APPLICATION_JSON)
        .body(BodyInserters.fromValue(newUser.toString()))
        .retrieve()
        .bodyToMono(String.class);
}
Delete user
    private
Mono < String > getUserSubId(String
accessToken, String
email
)
{
    String
    endpointUrl = "http://localhost:8080/auth/admin/realms/lambda/users?email=" + email;
    WebClient
    webClient = WebClient.builder().baseUrl(endpointUrl).build();
    return webClient.get()
        .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
        .retrieve()
        .bodyToMono(String.class)
        .map(response -> {
            JSONArray
            jsonArray = new JSONArray(response);
            JSONObject
            jsonObject = jsonArray.getJSONObject(0);
            return jsonObject.getString("id");
        });
}

private
Mono < String > deleteUserByUserid(String
accessToken, String
subId
)
{
    String
    endpointUrl = "http://localhost:8080/auth/admin/realms/lambda/users/";
    WebClient
    webClient = WebClient.builder().baseUrl(endpointUrl).build();
    return webClient.delete()
        .uri(subId)
        .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
        .retrieve()
        .bodyToMono(String.class);
}

Keycloak Rest API

quay.io/keycloak/keycloak:19.0.3-legacy

https://www.keycloak.org/docs-api/19.0.3/rest-api/index.html

http://localhost:8080/auth/realms/lambda/.well-known/openid-configuration

Get userinfo (POST | GET)

curl --location 'http://localhost:8080/auth/realms/lambda/protocol/openid-connect/userinfo' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJOMnF3eEFDTC0wVDFPYVJxR3JzTzlsdDFMeVpFejhHWlZMSm5GMkszMDdRIn0.eyJleHAiOjE2NzgxOTQwMDcsImlhdCI6MTY3ODE5MzcwNywiYXV0aF90aW1lIjoxNjc4MTkyMzQ1LCJqdGkiOiJmMDUyNjJmYi1jNjAyLTQxM2UtOWIzNi02MjViZjBiOTRlODYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbGFtYmRhIiwiYXVkIjpbInJlYWxtLW1hbmFnZW1lbnQiLCJicm9rZXIiLCJhY2NvdW50Il0sInN1YiI6IjhjNmUzNjM1LTVkODUtNGIwNS1hMTFmLTI4YWE0MzEwNmVjOCIsInR5cCI6IkJlYXJlciIsImF6cCI6InN0dWRlbnQtcG9ydGFsLWNsaWVudCIsIm5vbmNlIjoiNGZmOWU2MjItM2JjYi00MTc4LWE4YjktNjY3YTkzMmI1ODA1Iiwic2Vzc2lvbl9zdGF0ZSI6ImFkNzgyNWRmLTQ2MDYtNGZjNi05NjFhLTE4MTE5YjYxYmI4OCIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1sYW1iZGEiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiYWRtaW4tYWNjZXNzIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsicmVhbG0tbWFuYWdlbWVudCI6eyJyb2xlcyI6WyJ2aWV3LWlkZW50aXR5LXByb3ZpZGVycyIsInZpZXctZXZlbnRzIl19LCJicm9rZXIiOnsicm9sZXMiOlsicmVhZC10b2tlbiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsInZpZXctYXBwbGljYXRpb25zIiwidmlldy1jb25zZW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJtYW5hZ2UtY29uc2VudCIsImRlbGV0ZS1hY2NvdW50Iiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgYWRkcmVzcyBwaG9uZSBtaWNyb3Byb2ZpbGUtand0IG9mZmxpbmVfYWNjZXNzIHByb2ZpbGUiLCJzaWQiOiJhZDc4MjVkZi00NjA2LTRmYzYtOTYxYS0xODExOWI2MWJiODgiLCJ1cG4iOiJ1c2VyQGVtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhZGRyZXNzIjp7fSwibmFtZSI6InVzZXJAZW1haWwuY29tIHVzZXJAZW1haWwuY29tIiwiZ3JvdXBzIjpbImRlZmF1bHQtcm9sZXMtbGFtYmRhIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImFkbWluLWFjY2VzcyJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyQGVtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJ1c2VyQGVtYWlsLmNvbSIsImZhbWlseV9uYW1lIjoidXNlckBlbWFpbC5jb20iLCJlbWFpbCI6InVzZXJAZW1haWwuY29tIn0.jPMpi-xjyJDSOFFcuonkXrhWEsrThtMdw4NAPFjhCMOp_rTEHUpyAVbvR9mer8FCb5-gb-OX39c016_xxnheXjmBMdSXpZ0G4Ad26gMyp3k_fmQu8-d3oJdasu0MtSkytGq4NVSEsSi1Zv5pRSUv_rnAHtoo_9xJYiMqMo0_cwy6QnPpqhc18G5yWyH3m5YbvToIcgvXYnNa5xpyz9v0L9ilVNmB25sx5iPWTRth2j53bUqZMiCSpT1Bui_QRk0xoXXI2v-yBt9q9RfAv4TYcO-VHlOmhf_RoUyq5PF9uQKPqNxXPJy882nqiJaAAdTAXCiQe9Sf5n7duZZZJbgnqQ'

Get Token (POST)

curl --location 'http://localhost:8080/auth/realms/lambda/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=user@email.com' \
--data-urlencode 'password=user@email.com' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=student-portal-client'
--data-urlencode 'otp=262004'

Refresh Token (POST)

curl --location 'http://localhost:8080/auth/realms/lambda/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'client_id=<clientId>' \
--data-urlencode 'refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhZjQwNjEyZS01NmFmLTQ3N2ItYWIyNy0wNDBkNTA0ZDFhNjYifQ.eyJleHAiOjE2NzczNjU3MzUsImlhdCI6MTY3NzM2MzkzNSwianRpIjoiZGIzYTM0NmItN2YwZC00YmFkLTgyODMtNDJiYTYyNjFkYTc1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmNoYWluY3VldC5jb20vYXV0aC9yZWFsbXMvY2hhaW5ib3QiLCJhdWQiOiJodHRwczovL2F1dGguY2hhaW5jdWV0LmNvbS9hdXRoL3JlYWxtcy9jaGFpbmJvdCIsInN1YiI6IjI2N2U5ODU5LTMzNTEtNDE5NC05YWU3LWY3M2E1ZWQ5ZWYxMSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJjaGF0Ym90LWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI2NzhkZDAzNS0yZTYxLTQxNWEtOWUyMy04ZTBjYjFkOGJlOTEiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiNjc4ZGQwMzUtMmU2MS00MTVhLTllMjMtOGUwY2IxZDhiZTkxIn0.9YhXN4QOALYKzR-cuggZ-BCicUWAd-93pAV85MYlfSc' \
--data-urlencode 'client_secret=<secretId>'

Logout

# Get idToken from the token response
http://localhost:8080/auth/realms/lambda/protocol/openid-connect/logout?id_token_hint=idToken&post_logout_redirect_uri=http://localhost:3000`;

Create a user

# Create a user in Master realm 'createuseradmin'
# Create a realm-role 'create-user' and assign <realm>manage-users role and assign it to the user
# Get admin-cli token from keycloak for the user 'createuser@email.com' (POST)
curl --location 'http://localhost:8080/auth/realms/lambda/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=createuser@email.com' \
--data-urlencode 'password=createuser@email.com' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=admin-cli'

# Create user using admin-cli token (POST)
curl --location 'http://localhost:8080/auth/admin/realms/lambda/users' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJwZmpNdkdTbUtZcGtIRWVBTnBzeXFsdnU2Q2V2bzNId3JYTEo1R2RsMWM4In0.eyJleHAiOjE2NzgzNzA5MzIsImlhdCI6MTY3ODM3MDg3MiwianRpIjoiMjIyMWI4MmEtNzNlNC00MzJjLTk5MzUtYjhjMzE3ZDI1MDVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjRjYzhlNTFkLTlmNjEtNGJlZC1iODA0LTk0ZWFiYTY2MGI1MiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiI1YmYwMmZhMy00ODVhLTQ5YzItOWU2My1jM2ZlZWNjNTU0MGUiLCJhY3IiOiIxIiwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNWJmMDJmYTMtNDg1YS00OWMyLTllNjMtYzNmZWVjYzU1NDBlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIn0.M8K8_5PEc5oYosIrP_JKmF-5u8aLKq_rdGqkgiRdfD-OOCoWVxBy_avgSF4um8ohQ6PbK52IFKFueIPrD-bfQ5e7rLEQ3tUINSAIx1zZv7pxs50DCC_90GXWbPobuoZKziyG33mgY6eGNv2pzdYmkN3VLmft0pmpLA2occILwi1mhte8yLk2OQrczlmSRgq5y4AydS8bWBC2aEqJx9-_DA-Lolit4uc4wMS_0CoDpZL6BlRLOJz4ABqnmIh3DjyL2B42MTCsnKH-__cVZqndeZ5WlmjF9c0in3pfwgdE4R0d6sxh11zOI6flV7NyDMY66cczWOOHX4B7M-XaCK13QQ' \
--data-raw '{
    "enabled": true,
    "username": "test@gmail.com",
    "email": "test@gmail.com",
    "firstName": "test@gmail.com",
    "lastName": "test@gmail.com",
    "groups": [
        "user"
    ],
    "credentials": [
        {
            "type": "password",
            "value": "123",
            "temporary": false
        }
    ]
}'

Get userId by email (GET)

curl --location 'http://localhost:8080/auth/admin/realms/lambda/users?email=email@email.com' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2Nzg3MDQ0MjAsImlhdCI6MTY3ODcwNDEyMCwianRpIjoiODMwY2UwZDAtZjg0YS00NTUxLTljMDctMzc1MmE5M2UyNzczIiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJzdWIiOiIxNzgzOTI4OS1hNDcxLTRjMjItYjc2MC02OGIwM2U2YzYzMzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiY2Q2ZDcwYzgtYzQyYi00N2UzLTgxNDAtNTNmMTQ4YmVmNzAxIiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ik1hbmFnZSBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibWFuYWdldXNlciIsImdpdmVuX25hbWUiOiJNYW5hZ2UiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6Im1hbmFnZXVzZXJAZW1haWwuY29tIn0.DM1U49hbqU1k-_K7e5seqJWvUg6Z6E_RZeoJ05OpmUPBswJ7vkjSXsOuB56eO1x5nvjxWsWNCcxF7A7ArsmfwyrcuiSbS3iWL7YG-U0wEBi_mXgN0OnEKPYhPpT8R64dAfmzSOZrl57Ia8lnoY0YcvxQEUjbqi36lUGuhuaFK5ZqzYQvOH0BAzu5Xg2Dg8QzJEupt6Isr0QNj7O__R-8f1QfSV577jyNCx1q3Z-rAzIhpjIB9DG-4Sxe0HvUCsRNsKRQpNZbHLMfOJ-5xR3R0fx9LthlYw8rkBq863YrxAuUT5wk0V4JWzJur8MnRME8YQ1gvdNKl2gRLsjXZSNhyw'

Delete user by userId (DELETE)

curl --location --request DELETE 'http://localhost:8080/auth/admin/realms/lambda/users/55a0adfc-b5e5-425d-afc2-c37814ba4a15' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2Nzg3MDQ4MTAsImlhdCI6MTY3ODcwNDUxMCwianRpIjoiMDFmYjEwNTYtYzdjNS00OWRhLWE0Y2UtZWM3YzY3YjE3ZDE5IiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJzdWIiOiIxNzgzOTI4OS1hNDcxLTRjMjItYjc2MC02OGIwM2U2YzYzMzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiYzUzNzE2N2EtNTNiYS00ZTRhLThiNzktYzg5NGYzYTcyZTU0IiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ik1hbmFnZSBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibWFuYWdldXNlciIsImdpdmVuX25hbWUiOiJNYW5hZ2UiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6Im1hbmFnZXVzZXJAZW1haWwuY29tIn0.cklfxHDVKXAaH9f0O1cZLMwP09nrKVvLM_Xo3taTV7-VhRrdWjiJn7ZWaAyFYEh91fQAaNZwnkkO-dnQtKWH41RZqlBAQzXUlJ8rRmf0xPf8vzYeitxLxCYKmNqa3TWPAE0Z873cUHSZ7AFJOy9xGVpLsJBgbItJ1NrlmQEjBlETnh6xTFwOFcLoPGNk4SDfkA16-Ts9Ko6p25TDEnsTsHnVsftC5aVcnZSyrq3JTVXA4Vq5iMN7EuJwlh6XZBOoEAMZVoWYNN_AaEbyuYo2lMxcwxkuDtQFLs_V9Q1j52qHt1za1ZnQpJjkNGtHG7cpePVbLRuFL0AANSdHkwhFeg' \
--data ''

View Roles of a realm

  • Use admin-cli token
  • manage-realm role needs to be assigned to admin-users
  • view-realm is enough as role
curl --location 'http://localhost:8080/auth/admin/realms/lambda/roles' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2OTgyMjgyMDYsImlhdCI6MTY5ODIyNzkwNiwianRpIjoiM2RkZDlmMDItZmJkMS00ZjAxLTgyZDctNDkyMTI2ZWZmZDM1IiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJzdWIiOiIxNzgzOTI4OS1hNDcxLTRjMjItYjc2MC02OGIwM2U2YzYzMzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiYjk3YjVhZDYtMDRiYi00ZDM2LTk0MjctMDNjMTJkNTMzNDk4IiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWFuYWdlIFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJtYW5hZ2V1c2VyQHNlbnNlcmEuc2UiLCJnaXZlbl9uYW1lIjoiTWFuYWdlIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJtYW5hZ2V1c2VyQHNlbnNlcmEuc2UifQ.fZuzVpy0vNJOc24yKTTyIqx8RdXQuaI2Q901lXhxpxmeENmEG7QqVDFNkJyT-9bW0hTcGFFjTIDKzd931qK0bVQUm8u2rV3i8WeXkF4zVVACLHTGy0JKTixvGux63poJXdkm_Tlrcy68o5IM10BKkwiUyR4Hdg_ktosXoazoJwOWrUVVVxzxD8XmMPYPCwoKAYD-O6rzNcLkia1nuvRoVC7aRCPbp0-G8lF622OQAHzFwHgHvVSFPjFgL7XPAwpKeLQmf5VhFvhvxW25LYVaGNvd0Td87l3JdXl9GRkGXLSZelmUslazHCwMOxBMj7NAW7rME7lSX3uKJSXWFZhSaQ'

Add Realm roles to a user

  • Use admin-cli token
  • manage-users role needs to be assigned to admin-user
curl --location 'http://localhost:8080/auth/admin/realms/lambda/users/<user_id>/role-mappings/realm' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2OTgyMzQ2ODIsImlhdCI6MTY5ODIzNDM4MiwianRpIjoiOWY4MDFkNTUtNGNjOS00ZjI4LWJkM2QtZDY2MDhkMmJiYmQxIiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJzdWIiOiIxNzgzOTI4OS1hNDcxLTRjMjItYjc2MC02OGIwM2U2YzYzMzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiMmM4Mzg0ZWYtMDY0Ni00OWVkLWIxOTctNDE3OTM2YmE5YzZlIiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTWFuYWdlIFVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJtYW5hZ2V1c2VyQHNlbnNlcmEuc2UiLCJnaXZlbl9uYW1lIjoiTWFuYWdlIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwiZW1haWwiOiJtYW5hZ2V1c2VyQHNlbnNlcmEuc2UifQ.do-EeYt2Ayd7-pSlYyA85efHaDV_PMQVPS9zbDNolI4DIsxk77PCamVBnlzQ0HfKeisqmOBFt89lQuf7_asktu3I3K9wPaVeZ2WRZsAJRhKbELAaNjMGNYPauiOunMbcn4oOfq_XvQGd5QAbbOh4NGkPBzw3FNRiN9osvtD371MOUBKv2rpj06288aGZ7OCqfoIvs7wd5oGAdBbSyZwBbgFAOFmymqUlO-4_lP7CO9CBqXwUCKcP3UNJgVqQkcGPUmBrVDB-wEfWyC3aXYGvE6oHfb6Mc3DGoND6MgRdSOAX4szjNPMiXWivwHzOMLraav2uQu5yOOAPK_v51k_46Q' \
--data '[
    {
        "id": "id",
        "name": "role",
        "composite": false,
        "clientRole": false,
        "containerId": "lambda"
    }
]'

Update user using admin privilegios (PUT)

curl --location --request PUT 'http://localhost:8080/auth/admin/realms/lambda/users/67958fa7-e7a4-43fb-8c60-432169bf1d07' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2Nzg3MTM4NzgsImlhdCI6MTY3ODcxMzU3OCwianRpIjoiMWIzMmE1M2UtODQyNC00Zjc0LTk1NDgtY2M4ZmVmNWIyMDRjIiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJzdWIiOiIxNzgzOTI4OS1hNDcxLTRjMjItYjc2MC02OGIwM2U2YzYzMzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiOTljOTg5MDEtYzZhNi00Mjg4LTlhMGMtMDkzZGM2MzgxMWI3IiwiYWNyIjoiMSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ik1hbmFnZSBVc2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibWFuYWdldXNlciIsImdpdmVuX25hbWUiOiJNYW5hZ2UiLCJmYW1pbHlfbmFtZSI6IlVzZXIiLCJlbWFpbCI6Im1hbmFnZXVzZXJAZW1haWwuY29tIn0.jKioR0YuuR3ZkioYTbXwYDTbCLcS1MlVX03pV4WpZnOmzJ1llQdkEqJjr9beLS7LxrgyQfi-DeGIZcWAWcbG5mT-OLUiXNAOvWHlDhbQ7N4TKxIb7DiTolc_Sue3vv6l_SXA6XZsm6StIK3pOSdlwIZJqSIqszA9QB-nRAWVj4MvPLL9yWrX9o-PnZy3NN8Sttt1m9WW_kPTvgI0j-egOzuFZd646EWr0SQ1DrBDS22GsXil3kGWro3MTDqNIwyDu-5ycG8bNyPMPSm4iL4X60SSgkVR5FBz-Y5_ajKQ_jOW5j3tsZsUOV7ATPZ_mQiQXFOlEMOZuiOjn-kuOimZcA' \
--data-raw '{
    "firstName": "newFirstname",
    "lastName": "newLastname",
    "email": "new@email.com",
    "credentials": [
        {
            "type": "password",
            "value": "newPassword"        
        }
    ]
}'

Update user using account endpoint (POST) Use user access token

curl --location 'http://localhost:8080/auth/realms/lambda/account' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2Nzg3MjE2MzcsImlhdCI6MTY3ODcyMTMzNywianRpIjoiY2VlNGM0YjgtZTRhMy00ODI5LTgzNTItZjY2MmRiNjMwZTEzIiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiZGI2MTQ2YTItNDBkNy00MmUxLTkyMTctMzJkNTI4MTM1NTIwIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3R1ZGVudC1wb3J0YWwtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjNjMmM5YzQ5LTAxYTItNDZiNS05NTQ2LWVkMzk5NTkxY2EwNiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Im5ld0ZpcnN0bmFtZSBsYXN0TmFtZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3RAZ21haWwuY29tIiwiZ2l2ZW5fbmFtZSI6Im5ld0ZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdE5hbWUiLCJlbWFpbCI6InRlc3RAZ21haWwuY29tIn0.PDDTJZjlVfQH0ZNKysHNl-erAZ22bpO_-Q9TZ3gcTxgArQsJw85PKCAgUXn_KY0YjUHXi9yclHXBdcrkUkwFj39D_8fr4OWFTvpONM1B-Sig19thjd9byQfmdADBV3_C3osb1xIr0ClY3xTPRU3oXMVvpZOV13ELJBUUfftUgJMSYO_fpsETCWx-SADGWLbfjEslAbYjKWKsnXnRph8Qu14QNCuve7lKtjLYBv3UDiirOoQB3UeTpjl8NaPxc6cupgCOFc_CEi3XwTI3w6r0mrUgjBY0DBcaPs7HA2axE35v9lR6w6swaxEQFh4D2AjGwiq5eEBg3geVkAA57wfzKw' \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName": "newFirstname",
    "lastName": "lastName",
    "email": "test@gmail.com"
}'

Get account credentials (GET) Update password depricated using cli

curl --location --request GET 'http://localhost:8080/auth/realms/lambda/account/credentials' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJmQ3JMUHhXUVJsZVh1NHkwMHVVV2RkVkJtSlZkMDVJSUZVZ3dpM0pwMjVvIn0.eyJleHAiOjE2Nzg4ODUxMTUsImlhdCI6MTY3ODg4NDgxNSwianRpIjoiMzQwY2U3NTEtOTRlNS00YzUwLTljOGItZWJlYzZiODM5M2M2IiwiaXNzIjoiaHR0cHM6Ly9pYW0uc2Vuc2VyYS5zZS9hdXRoL3JlYWxtcy9sYW1iZGEiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiZGI2MTQ2YTItNDBkNy00MmUxLTkyMTctMzJkNTI4MTM1NTIwIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3R1ZGVudC1wb3J0YWwtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjIzMWYwOGFmLThlODEtNGRlYy05MWI3LTMzZWVhNDRiZThhYiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoibmV3Rmlyc3RuYW1lIG5ld0xhc3RuYW1lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVzdEBnbWFpbC5jb20iLCJnaXZlbl9uYW1lIjoibmV3Rmlyc3RuYW1lIiwiZmFtaWx5X25hbWUiOiJuZXdMYXN0bmFtZSIsImVtYWlsIjoidGVzdEBnbWFpbC5jb20ifQ.YGY5u2IZASf2tS7AIvG9diHhSd9DC38fhsQUknmKFfVYIC5KZ1igV5F1ccMj3DDF86Sf_v3jaTVmSav_hWpxptjpO3ZVkFKUmQ4xmkCJARjF8PCzBDibEixpyz8L7ON2nAszM8PPZMspG7SXWQydC_vnvW6QdcM1DYQ8EPArEa_RWQ-T1SK7altXqcl0vzYvlKbcPzaKUco-_YgH-ge_3RmpRQ8i-jNGWqqufL11Sr43WwGY-OA9Hrb-FbEH22RD3nrHAVbiKQ50fh_nRoDxIWaRiu2V5j4YBJ-uVD2LRPZqMXSf0VFO6zb4xSgGcLdVOlFTxuG0y5_82aNXm3jzww' \
--header 'Content-Type: application/json' \
--data '{}'

Reset password

const url = "http://localhost:8080/auth/realms/lambda/protocol/openid-connect/auth?client_id=<client_id>&redirect_uri=http://localhost:3000/&response_type=code&scope=openid&kc_action=UPDATE_PASSWORD";
window.location.href = url;