3

I am implementing Keycloak authorization to my Node.js application. I created a realm and a client inside of it. I don't have any roles either in realm or in client. I have a route "/test" which is protected with Keycloak.

My keycloak.json looks like this:

{
  "realm": "demo",
  "auth-server-url": "http://localhost:8080/auth",
  "ssl-required": "external",
  "resource": "myapp",
  "public-client": true,
  "confidential-port": 0
}
SparkFountain
  • 2,110
  • 15
  • 35
Praveen Reddy
  • 31
  • 1
  • 1
  • 3
  • 1
    I am having exactly the same issue. Tried to use the config `"bearer-only": true` but that didn't work either, not even presenting Keycloak's login screen. – SparkFountain Sep 03 '19 at 13:29

2 Answers2

2

Define roles (realm roles or client roles), assign the roles to your user (user which you use to perform the test), check your role scope mappings (or you can configure your client as full-scope for testing purpouse), and check that you are protecting your '/test' route with those same permissions.

E.g. you can try with keycloak-nodejs adapter (example).

Look at this configuration file (keycloak realm configuration example):

{
    "realm": "nodejs-example",
    "enabled": true,
    "sslRequired": "external",
    "registrationAllowed": true,
    "privateKey": "...................",
    "publicKey": ".....................",
    "requiredCredentials": [ "password" ],
    "users" : [
        {
            "username" : "user",
            "enabled": true,
            "email" : "sample-user@nodejs-example",
            "firstName": "Sample",
            "lastName": "User",
            "credentials" : [
                { "type" : "password",
                  "value" : "password" }
            ],
            "realmRoles": [ "user" ],
            "clientRoles": {
                "account": ["view-profile", "manage-account"]
            }
        }
    ],
    "roles" : {
        "realm" : [
            {
                "name": "user",
                "description": "User privileges"
            },
            {
                "name": "admin",
                "description": "Administrator privileges"
            }
        ]
    },
    "scopeMappings": [
        {
            "client": "nodejs-connect",
            "roles": ["user"]
        }
    ],
    "clients": [
        {
            "clientId": "nodejs-connect",
            "enabled": true,
            "publicClient": true,
            "baseUrl": "/",
            "adminUrl" : "http://localhost:3000/",
            "baseUrl" : "http://localhost:3000/",
            "redirectUris": [
                "http://localhost:3000/*"
            ],
            "webOrigins": []
        },
        {
            "clientId": "nodejs-apiserver",
            "enabled": true,
            "secret": "secret",
            "redirectUris": [
              "http://localhost:3000/*"
            ],
            "webOrigins": [
              "http://localhost:3000/*"
            ],
            "serviceAccountsEnabled": true,
            "authorizationServicesEnabled": true,
            "authorizationSettings": {
              "resources": [
                {
                  "name": "resource",
                  "type": "urn:nodejs-apiserver:resources:default",
                  "ownerManagedAccess": false,
                  "uris": [
                    "/*"
                  ],
                  "scopes": [
                    {
                      "name": "view"
                    },
                    {
                      "name": "write"
                    }
                  ]
                }
              ],
              "policies": [
                {
                  "name": "Default Policy",
                  "description": "A policy that grants access only for users within this realm",
                  "type": "js",
                  "config": {
                    "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
                  }
                },
                {
                  "name": "Default Permission",
                  "description": "A permission that applies to the default resource type",
                  "type": "resource",
                  "config": {
                    "defaultResourceType": "urn:nodejs-apiserver:resources:default",
                    "applyPolicies": "[\"Default Policy\"]"
                  }
                }
              ]
            }
          }
    ]
}

Explanation:

From this sample, look at how a user has roles assigned (realm roles: "user", account client roles: "account": ["view-profile", "manage-account"]):

{
...
       "users" : [
            {
                "username" : "user",
                "enabled": true,
                "email" : "sample-user@nodejs-example",
                "firstName": "Sample",
                "lastName": "User",
                "credentials" : [
                    { "type" : "password",
                      "value" : "password" }
                ],
                "realmRoles": [ "user" ],
                "clientRoles": {
                    "account": ["view-profile", "manage-account"]
                }
            }
        ],
    ...
    }

Look at how realm Roles are defined:

{
...
    "roles" : {
        "realm" : [
            {
                "name": "user",
                "description": "User privileges"
            },
            {
                "name": "admin",
                "description": "Administrator privileges"
            }
        ]
    },
...
}

Look at how this sample uses a 'scope mapping' to map roles from realm to a user authenticated by a client (read more about this here role scope mapping):

{
...
    "scopeMappings": [
        {
            "client": "nodejs-connect",
            "roles": ["user"]
        }
    ],
...
}

Look at how clients are defined. Check that 'nodejs-connect' client is public and 'nodejs-apiserver' is secret. In this sample, server is using the 'Authorization Api' to protect resources but you could protect your resources by granted roles only (if you want).

{
...
    "clients": [
        {
            "clientId": "nodejs-connect",
            "enabled": true,
            "publicClient": true,
            "baseUrl": "/",
            "adminUrl" : "http://localhost:3000/",
            "baseUrl" : "http://localhost:3000/",
            "redirectUris": [
                "http://localhost:3000/*"
            ],
            "webOrigins": []
        },
        {
            "clientId": "nodejs-apiserver",
            "enabled": true,
            "secret": "secret",
            "redirectUris": [
              "http://localhost:3000/*"
            ],
            "webOrigins": [
              "http://localhost:3000/*"
            ],
            "serviceAccountsEnabled": true,
            "authorizationServicesEnabled": true,
            "authorizationSettings": {
              "resources": [
                {
                  "name": "resource",
                  "type": "urn:nodejs-apiserver:resources:default",
                  "ownerManagedAccess": false,
                  "uris": [
                    "/*"
                  ],
                  "scopes": [
                    {
                      "name": "view"
                    },
                    {
                      "name": "write"
                    }
                  ]
                }
              ],
              "policies": [
                {
                  "name": "Default Policy",
                  "description": "A policy that grants access only for users within this realm",
                  "type": "js",
                  "config": {
                    "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
                  }
                },
                {
                  "name": "Default Permission",
                  "description": "A permission that applies to the default resource type",
                  "type": "resource",
                  "config": {
                    "defaultResourceType": "urn:nodejs-apiserver:resources:default",
                    "applyPolicies": "[\"Default Policy\"]"
                  }
                }
              ]
            }
          }
    ]
...
}

Finally, look at javascript (index.js) file to see how it uses 'keycloak-connect' (adapter) to protect and enforce access policies (usage of Authorization Api).

Tip

In development, you can get an access token and use this page to decode and see the token's content.

JWT.IO

I hope this helps.

Ariel Carrera
  • 5,113
  • 25
  • 36
  • You might want to remove/edit the private and public keys from your example, in case you included it accidentally. – parsecer Sep 06 '19 at 00:07
  • 1
    Thank you @parsecer, it is a sample configuration and it is in a public github repo! – Ariel Carrera Sep 06 '19 at 01:15
  • 1
    I'm running exactly that "official" example with the imported example realm, and I still get Access Denied 403 on the /protected resource, just like in my own setup. Tried with keycloak:10.0.2 and keycloak:9.0.3. – Thomas Jul 06 '20 at 22:24
  • 1
    I am having the same issue. Does anyone resolve this issue? – Shamim Nov 29 '20 at 19:01
0

I also had a 403 - Access Denied. What helped was stepping through the auth code of keycloak-connect. The token validation happens in the function validateToken in node_modules\keycloak-connect\middleware\auth-utils\grant-manager.js

if (!token) {
  reject(new Error('invalid token (missing)'))
} else if (token.isExpired()) {
  reject(new Error('invalid token (expired)'))
} else if (!token.signed) {
  reject(new Error('invalid token (not signed)'))
} else if (token.content.typ !== expectedType) {
  reject(new Error('invalid token (wrong type)'))
} else if (token.content.iat < this.notBefore) {
  reject(new Error('invalid token (stale token)'))
} else if (token.content.iss !== this.realmUrl) {
  reject(new Error('invalid token (wrong ISS)'))
}
...

For me, the problem was that I configured the url to the keycloak server without the schema (https). token.content.iss includes the schema and so it rejected because of token.content.iss !== this.realmUrl.

Unfortunately they seem to swallow those valuable Error messages without logging or returning them. I opened a question regarding this behavior.

andymel
  • 4,538
  • 2
  • 23
  • 35