Keycloak: 401,"error":"Unauthorized","message":"Unauthorized"

I’m facing the same issue with OpenID like many others. Checked a lot of related topics here, but have not found a solution. My Opensearch and Openseach-dashboards v. 2.10.
Keycloak is 26.2.
When I’m trying to login through Keycloak I get “401 Unauthorized with Keycloak OpenID”. In keycloak logs:
2025-08-20 11:51:28,371 WARN [org.keycloak.services] (executor-thread-139) KC-SERVICES0046: Multiple values found ‘[view-realm, view-identity-providers, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
(as I can see in the log above there is no my os_role “os_admin“ in the list I created within the client opensearch1.

My keycloak client config attached:

{
  "clientId": "opensearch1",
  "name": "opensearch1",
  "description": "",
  "rootUrl": "https://keyvault.mycorp.local/",
  "adminUrl": "https://keyvault.mycorp.local/",
  "baseUrl": "",
  "surrogateAuthRequired": false,
  "enabled": true,
  "alwaysDisplayInConsole": false,
  "clientAuthenticatorType": "client-secret",
  "secret": "vjbLwYK9ANfSWLmE7yxxoJic3pcFL16E",
  "redirectUris": \[
    "https://logging.mycorp.local/*"
  \],
  "webOrigins": \[
    "https://logging.mycorp.local"
  \],
  "notBefore": 0,
  "bearerOnly": false,
  "consentRequired": false,
  "standardFlowEnabled": true,
  "implicitFlowEnabled": true,
  "directAccessGrantsEnabled": true,
  "serviceAccountsEnabled": true,
  "authorizationServicesEnabled": true,
  "publicClient": false,
  "frontchannelLogout": true,
  "protocol": "openid-connect",
  "attributes": {
    "realm_client": "false",
    "oidc.ciba.grant.enabled": "false",
    "client.secret.creation.time": "1755247233",
    "backchannel.logout.session.required": "true",
    "standard.token.exchange.enabled": "false",
    "frontchannel.logout.session.required": "true",
    "oauth2.device.authorization.grant.enabled": "false",
    "display.on.consent.screen": "false",
    "backchannel.logout.revoke.offline.tokens": "false"
  },
  "authenticationFlowBindingOverrides": {},
  "fullScopeAllowed": true,
  "nodeReRegistrationTimeout": -1,
  "protocolMappers": \[
    {
      "name": "opensearch1-mapper",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usermodel-client-role-mapper",
      "consentRequired": false,
      "config": {
        "introspection.token.claim": "false",
        "multivalued": "true",
        "userinfo.token.claim": "true",
        "id.token.claim": "true",
        "lightweight.claim": "false",
        "access.token.claim": "true",
        "claim.name": "os_roles",
        "jsonType.label": "String",
        "usermodel.clientRoleMapping.clientId": "opensearch1"
      }
    }
  \],
  "defaultClientScopes": \[
    "web-origins",
    "service_account",
    "acr",
    "profile",
    "roles",
    "basic",
    "email"
  \],
  "optionalClientScopes": \[
    "address",
    "phone",
    "offline_access",
    "microprofile-jwt"
  \],
  "access": {
    "view": true,
    "configure": true,
    "manage": true
  }
}

My opensearch_dashboards.yml:

opensearch.hosts: \[http://localhost:9200\]
opensearch.ssl.verificationMode: none
opensearch.username: admin
opensearch.password: b6rb_nnm4_55Cx1
opensearch.requestHeadersWhitelist:
  \["Authorization", "securitytenant", "WWW-Authenticate"\]
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.enable_global: true
opensearch_security.multitenancy.tenants.enable_private: true
opensearch_security.multitenancy.tenants.preferred: \[Global, Private\]
opensearch_security.multitenancy.enable_filter: false
opensearch_security.readonly_mode.roles: \[kibana_read_only\]
\# Use this setting if you are running opensearch-dashboards without https
opensearch_security.cookie.secure: false
\# keycloak:
opensearch_security.auth.multiple_auth_enabled: true
opensearch_security.auth.type: \["basicauth", "openid"\]
opensearch_security.ui.openid.login.buttonname: "Log in with Keycloak"
opensearch_security.openid.connect_url: https://keycloak.mycorp.local/auth/realms/master/.well-known/openid-configuration
opensearch_security.openid.base_redirect_url: https://logging.mycorp.local
opensearch_security.openid.client_id: opensearch1
opensearch_security.openid.client_secret: vjbLwYK9ANfSWLmE7yxxoJic3pcFL16E
opensearch_security.openid.verify_hostnames: false
opensearch_security.openid.refresh_tokens: false
#opensearch_security.openid.scope: "opensearch1-dedicated"
opensearch_security.openid.header: "Authorization"
opensearch_security.cookie.ttl: "3600"

My opensearch-security/config.yml:

    authc:
      \# <1>
      basic_internal_auth_domain:
        description: "Authenticate via HTTP Basic against internal users database"
        http_enabled: true
        transport_enabled: true
        order: 0
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          type: intern
      \# <2>
      openid_auth_domain:
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: openid
          challenge: true
          config:
            subject_key: preferred_username
            roles_key: os_roles
            openid_connect_url: https://keycloak.mycorp.local/auth/realms/master/.well-known/openid-configuration
            #scope: "openid profile email"
            openid_connect_idp:
              enable_ssl: true
              verify_hostnames: false
            jwt_clock_skew_tolerance_seconds: 60
            client_id: opensearch1
            client_secret: vjbLwYK9ANfSWLmE7yxxoJic3pcFL16E
        authentication_backend:
          type: noop
      \#

And internal_users.yml:

---
\_meta:
  type: "internalusers"
  config_version: 2
\# Define your internal users here
admin:
  hash: "$2y$12$S1.EVgMoneoDhT5\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*WFM9aKpIuDBD12d2"
  reserved: true
  backend_roles:
    - "admin"
    - "os_admin"
  description: "Demo admin user"
kibanaro:
  hash: "$2a$12$JJSXNfTowz7Uu5ttXfeYpeYE0arACvcwlPBStB1F.MI7f0U9Z4DGC"
  reserved: false
  backend_roles:
    - "kibanauser"
    - "readall"
    - "os_kibanauser"
  attributes:
    attribute1: "value1"
    attribute2: "value2"
    attribute3: "value3"
  description: "Demo OpenSearch Dashboards read only user, using external role mapping"
readall:
  hash: "$2a$12$ae4ycwzwvLtZxwZ82RmiEunBbIPiAmGZduBAjKN0TXdwQFtCwARz2"
  reserved: false
  backend_roles:
    - "readall"
    - "os_readall"
  description: "Demo readall user, using external role mapping"

Could somebody be so kind and help my find a solution?

@wh1test This is expected. These are only labels and you can either use it to assign Realm roles and make it a composite role.

Or, create Realm roles and then assign to the user directly.

Could you send screen shot of your client scopes full list in opensearch1 client?

Thank you very much, Pablo. I’ll check your suggestions and reply.
Here is my list (recently I tried to remove all roles except dedicated), but it didn’t help. Therefore I re-joined them as Optional.

@wh1test Thank you.
Please access roles, go to Mappers and take a screenshot of that list.
If you have os_roles in it, please access it and take a screenshot of that too.

Here is Mappers of my client:

Here is my real roles (default):

My user is associated with the role os_admin:

A great thing in Keycloak that is somewhat hard to find but useful when debugging auth is this:

Scroll down and look in `groups` or what field you have in your settings.

Br Sebastian

1 Like

great tool!

@wh1test You’ve used User Client Role. Try creating a mapper of the User Realm Role type.
Also, your os_roles has the Multivalued option disabled. As a result, you get the reported error. Opensearch won’t handle an array as it needs single values.

Hi Pablo. It’s not clear for me =| I’m trying to configure keycloak client from scratch.

Added it into my fresh client config.

I tried Multivalued in ON and OFF states without luck.

P.S. Initially I tried to sutup using the guide: Enabling OpenSearch OIDC Authentication for Single Sign-On

It’s nightmare =\ I’ve created client from scratch, created roles there and created realm role “os-admin”. Then associated it with client role, but still unable to pass through authentication: {“statusCode”:401,“error”:“Unauthorized”,“message”:“Unauthorized”}

Full config of my keycloak client opensearch1:





Those messages are in keycloak log:
2025-08-21 12:55:28,217 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-realm, view-identity-providers, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value

rest logs

2025-08-21 12:55:28,218 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,218 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,218 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[manage-account, manage-account-links, view-profile]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,219 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-realm, view-identity-providers, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,219 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,219 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-21 12:55:28,219 WARN [org.keycloak.services] (executor-thread-34) KC-SERVICES0046: Multiple values found ‘[manage-account, manage-account-links, view-profile]’ for protocol mapper ‘client roles’ but expected just single value

Here is output of Client scope Evaluate tool. BTW I don’t understand why os_roles”: [ ] doesn’t contain “os_admin” role. =\
{
“exp”: 1755771764,
“iat”: 1755771704,
“jti”: “cdf6e473-c82f-5555-9999-4cd52780e741”,
“iss”: “https://keycloak.mycorp.com/auth/realms/master”,
“aud”: “opensearch1”,
“sub”: “8f805225-799b-3333-2222-ad35d6fa6a4c”,
“typ”: “ID”,
“azp”: “opensearch1”,
“sid”: “f2c2e56a-9461-4444-4444-c121634ce622”,
“acr”: “1”,
“resource_access”: {
“opensearch1”: {
“roles”: “os_admin”
},
“Parking-realm”: {
“roles”: “view-realm”
},
“external-realm”: {
“roles”: “view-identity-providers”
},
“master-realm”: {
“roles”: “view-identity-providers”
},
“account”: {
“roles”: “manage-account”
}
},
“email_verified”: false,
“os_roles”: [
“create-realm”,
“default-roles-master”,
“offline_access”,
“admin”,
“uma_authorization”
],
“name”: “MyNameMySurname”,
“preferred_username”: “myusername”,
“locale”: “en”,
“given_name”: “MyName”,
“family_name”: “MySurname”,
“email”: “****@mycorp.com
}

My opensearch_dashboards.yml:

opensearch_dashboards.yml
opensearch.hosts: \[http://localhost:9200\]
opensearch.ssl.verificationMode: none
opensearch.username: admin
opensearch.password: mypasss
opensearch.requestHeadersWhitelist: \[“Authorization”, “securitytenant”, “WWW-Authenticate”\]
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.enable_global: true
opensearch_security.multitenancy.tenants.enable_private: true
opensearch_security.multitenancy.tenants.preferred: \[Global, Private\]
opensearch_security.multitenancy.enable_filter: false
opensearch_security.readonly_mode.roles: \[kibana_read_only\]

# Use this setting if you are running opensearch-dashboards without https

opensearch_security.cookie.secure: false

\# keycloak:
opensearch_security.auth.multiple_auth_enabled: true
opensearch_security.auth.type: \[“basicauth”, “openid”\]
opensearch_security.ui.openid.login.buttonname: “Log in with Keycloak”
opensearch_security.openid.connect_url: [https://keycloak.mycorp.com/auth/realms/master/.well-known/openid-configuration](https://keycloak.mycorp.com/auth/realms/master/.well-known/openid-configuration)
opensearch_security.openid.base_redirect_url: [https://logging.mycorp.com](https://logging.mycorp.com)
opensearch_security.openid.client_id: opensearch1
opensearch_security.openid.client_secret: f1CZ7202gpLezh66666639yx5m0l
opensearch_security.openid.verify_hostnames: false
opensearch_security.openid.refresh_tokens: false
opensearch_security.openid.scope: “openid email profile”
opensearch_security.openid.header: “Authorization”
opensearch_security.cookie.ttl: “3600”

And my config/opensearch-security/config.yml:

config.yml
authc:
      # <1>
      basic_internal_auth_domain:
        description: “Authenticate via HTTP Basic against internal users database”
        http_enabled: true
        transport_enabled: true
        order: 0
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          type: intern

openid_auth_domain:
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: openid
          challenge: true
          config:
            subject_key: preferred_username
            roles_key: os_roles
            openid_connect_url: https://keycloak.mycorp.com/auth/realms/master/.well-known/openid-configuration
            scope: “openid email profile”
            openid_connect_idp:
              enable_ssl: true
              verify_hostnames: false
            jwt_clock_skew_tolerance_seconds: 60
            client_id: opensearch1
            client_secret: f1CZ7202gpLez666666639yx5m0l
        authentication_backend:
          type: noop

My roles_mapping.yml:

roles_mapping.yml
all_access:
  reserved: false
  backend_roles:
    - “admin”
    - “os_admin”
  description: “Maps admin to all_access”
kibana_user:
  reserved: false
  backend_roles:
    - “kibanauser”
    - “os_kibanauser”
  description: “Maps kibanauser to kibana_user”
readall:
  reserved: false
  backend_roles:
    - “readall”
    - “os_readall”

And my internal_users.yml:

internal_users.yml
admin:
  hash: “$2y$12$S1.EVgMone\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*DBD12d2”
  reserved: true
  backend_roles:
    - “admin”
    - “os_admin”
  description: “Demo admin user”
kibanaro:
  hash: “$2a$12$JJSXNfTowz7Uu55555555555555F.MI7f0U9Z4DGC”
  reserved: false
  backend_roles:
    - “kibanauser”
    - “readall”
    - “os_kibanauser”
  attributes:
    attribute1: “value1”
    attribute2: “value2”
    attribute3: “value3”
  description: “Demo OpenSearch Dashboards read only user, using external role mapping”
readall:
  hash: “$2a$12$ae4ycwzwvLtZxwZ82Rm8888888888888888N0TXdwQFtCwARz2”
  reserved: false
  backend_roles:
    - “readall”
    - “os_readall”
  description: “Demo readall user, using external role mapping”

Could somebody help? I cannot find out what is wrong =|
P.S. Opensearch and opensearch-dashboards (both are v 2.10) has been restarted after client re-created.

@wh1test JWT looks fine. When you click on the Log in with single sign-on button, are you redirected successfully to Keycloak?

@wh1test Try switching this

to this

Yes, Pablo. Everything works as expected: redirect to keycloak page, input login/password, input OTP and then {“statusCode”:401,“error”:“Unauthorized”,“message”:“Unauthorized”}.
Other clients works perfectly (I use integrations with vCloud, Grafana, ArgoCD, sonarqube, zabbix).

The same error((

@wh1test Do you use a reverse proxy between OSD and Keycloak?

Yes. Envoy. I can connect them without reverse proxy. Those hosts located in the same subnet, but all the rest Keycloak clients work properly =|

@wh1test Does your reverse proxy pass Authorization header as OpenSearch Dashboards does?

Okay, I’ve changed settings in config/opensearch-security/config.yml to access keycloak directly:
openid_connect_url: ``http://172.16.1.15:8080/auth/realms/master/.well-known/openid-configuration
and opensearch_dashboards.yml
opensearch_security.openid.connect_url: ``http://172.16.1.15:8080/auth/realms/master/.well-known/openid-configuration
.

Restart opensearch and opensearch-dashboards.

Login attempt failed:
This page isn’t working

172.16.1.15 redirected you too many times.

ERR_TOO_MANY_REDIRECTS

@wh1test Do you see any 401 in the logs?

This could be related to securitytenant header. That is also whitelisted in opensearch_dashboards.yml

@wh1test I’ve just noticed that you connect directly to your Keycloak, but issue is on the other end when Keycloak is redirecting back to OpenSearch Dashboards. In this case you’re still using reverse proxy to access OpenSearch Dashboards.

Yeap, Pablo you are right. Adjusted settings on opensearch-dashboards side:
opensearch_security.openid.base_redirect_url: ``http://172.16.1.7:5601
restarted opensearch-dashboards.
And keycloak client:


But still getting the same 401 error page and messages in keycloak logs.

logs

2025-08-22 16:56:28,189 WARN [org.keycloak.cookie.DefaultCookieProvider] (executor-thread-75) Non-secure context detected; cookies are not secured, and will not be available in cross-origin POST requests
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-realm, view-identity-providers, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[manage-account, manage-account-links, view-profile]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-realm, view-identity-providers, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[view-identity-providers, view-realm, manage-identity-providers, impersonation, create-client, manage-users, query-realms, view-authorization, query-clients, query-users, manage-events, manage-realm, view-events, view-users, view-clients, manage-authorization, manage-clients, query-groups]’ for protocol mapper ‘client roles’ but expected just single value
2025-08-22 16:56:28,211 WARN [org.keycloak.services] (executor-thread-75) KC-SERVICES0046: Multiple values found ‘[manage-account, manage-account-links, view-profile]’ for protocol mapper ‘client roles’ but expected just single value

Maybe I have to switch opensearch from opensearch.hosts: [http://localhost:9200] to opensearch.hosts: [https://localhost:9200] and adjust logstash settings =|