couchdb icon indicating copy to clipboard operation
couchdb copied to clipboard

JWT auth doesn't pick up roles

Open loylick opened this issue 4 years ago • 4 comments

Description

Hi! I've encountered an issue when using JWT authentication in couchdb. I had to compile Couchdb from sources to make JWT authentication work. I use the branch 3.x. First I tried to pass roles to couchdb through the custom claim _couchdb.roles. When I failed I looked at recent issues resolved and found that there was a new configuration field "roles_claim_name" in [jwt_auth] section. So, I added a line roles_claim_name = couchdbroles into [jwt_auth] section of local.ini file. The authentication succeeds, the username is passed correctly from the "sub" claim, but the roles are not passed from JWT token.

Steps to Reproduce

I used such request to get authenticated: curl -L -X GET 'http://127.0.0.1:5984/_session'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZN2lDWWlJSnZyMUdLV0d4bG9jVS1GSWhISVVLTHRXRFQ4RjFRczN3MWxVIn0.eyJleHAiOjE2MzIwNDQ5NTksImlhdCI6MTYzMjA0NDY1OSwianRpIjoiYmVmMzNhN2EtNjczMi00N2YwLTg3YmYtM2RmMDJmYWY0Yzk5IiwiaXNzIjoiaHR0cDovLzE1OC42OS4yNDguNjo4MTgwL2F1dGgvcmVhbG1zL3F1aW1hbmFnZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJiNWVmZWQ2Ny03MzFjLTQwOGYtOGMyMi1lMTgyMzA5Y2YzZWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJrZXltYW5hZ2UtYXBwIiwic2Vzc2lvbl9zdGF0ZSI6IjRjMGQ3YzIwLWFiZmUtNDI1OS05NmM4LTE2MDdkYzIxOWFhNSIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLXF1aW1hbmFnZSIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsia2V5bWFuYWdlLWFwcCI6eyJyb2xlcyI6WyJkZXZlbG9wcGVyIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiNGMwZDdjMjAtYWJmZS00MjU5LTk2YzgtMTYwN2RjMjE5YWE1IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJjb3VjaGRicm9sZXMiOlsiZGV2ZWxvcHBlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciJ9.csbf8SX0dqD8FjU9iwSWxfVxBMF3eEcS7GRDzJ2tvw1IipFAInb4a-abNFEB6rErcRYiONlAbGTLGBjqT2Z0TGabGXBxXCB3A4F5q243DIY9WUBAO9_xiFQpeQCgLUYhdY5ensU5tYipcXn5-kLxruwhqDqpEFnpYQsulmIYE3MV3xmcazsBTGBDPILOoZK5FGYKlN3IvULi4cEpU_PZQqE-NNDwG1mkzla-mbH-hFv8t9lnIs94M9j7sUehtBP8Lhx76WJZPJJ0BwUkqHrP6mICZq0z6fzC057TyqqzwoCbNFSlbDcLLNVEt4zIF4WCkq-O8t3b1xLXN1j7RGvowA'

the response I got: {"ok":true,"userCtx":{"name":"b5efed67-731c-408f-8c22-e182309cf3eb","roles":[]},"info":{"authentication_handlers":["jwt","cookie","default"],"authenticated":"jwt"}}

Expected Behaviour

I expect that the custon claim "couchdbroles: ["developer"] would be passed to couchdb and the response of couchdb should be: {"ok":true,"userCtx":{"name":"b5efed67-731c-408f-8c22-e182309cf3eb","roles":["developer"]},"info":{"authentication_handlers":["jwt","cookie","default"],"authenticated":"jwt"}}

Your Environment

my OS is: Linux 4.15.0-142-generic #146-Ubuntu SMP Tue Apr 13 01:11:19 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux curl http://127.0.0.1:5984 {"couchdb":"Welcome","version":"3.1.1-2871128","git_sha":"2871128","uuid":"45b6dd1db0fcd0ed1700cd671342978b","features":["access-ready","partitioned","pluggable-storage-engines","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundation"}} erlang version 1:24.0.5-1

Additional Context

I looked at the code of JWT parsing at: couch_httpd_auth.erl jwt_authentication_handler(Req) -> case header_value(Req, "Authorization") of "Bearer " ++ Jwt -> RequiredClaims = get_configured_claims(), case jwtf:decode(?l2b(Jwt), [alg | RequiredClaims], fun jwtf_keystore:get/2) of {ok, {Claims}} -> case lists:keyfind(<<"sub">>, 1, Claims) of false -> throw({unauthorized, <<"Token missing sub claim.">>}); {_, User} -> Req#httpd{ user_ctx = #user_ctx{ name = User, roles = couch_util:get_value( ?l2b( config:get( "jwt_auth", "roles_claim_name", "_couchdb.roles" ) ), Claims, [] ) } } end; {error, Reason} -> throw(Reason) end; _ -> Req end.

The only reason for it not to work, I can think of, is failing to parse my JWT correctly. The structure of JWT I'm using is somewhat more complicated than the structure you used for testing (just my assumption). I'm not familiar with erlang but if you could provide me some code I could insert for debugging purposes into the function jwt_authentication_handler it would help us to resolve the issue.

loylick avatar Sep 19 '21 10:09 loylick

Hi,

I've just tried this locally and it worked for me. Can you verify you set the config correctly by querying /_node/_local/_config/jwt_auth/roles_claim_name and confirm it returns "couchdbroles".

rnewson avatar Oct 29 '21 15:10 rnewson

I used jtw.io to create a JWT token signed with my key but with your verbatim (decoded) JWT body and get;

{"ok":true,"userCtx":{"name":"b5efed67-731c-408f-8c22-e182309cf3eb","roles":["developper"]},"info":{"authentication_handlers":["jwt","cookie","default"],"authenticated":"jwt"}}

rnewson avatar Oct 29 '21 15:10 rnewson

Hi, the response to /_node/_local/_config/jwt_auth/roles_claim_name is "_couchdb.roles". I've tried what you did. I called the claim "couchdbroles" without nesting the array. In such configuration it worked. So if I call the claim "_couchdb.roles" the roles are not passed. If I call the claim as you did, everything works fine.

loylick avatar Nov 03 '21 13:11 loylick

Hello. I'm trying to use JWT security access with a keycloak access token. But I can manage to make it work with this properly.

How is the syntax for json nested objects inside JWT token ?

[jwt_auth]
required_claims = exp, {iss, "http://localhost:9080/auth/realms/ovalt"}
roles_claim_name = resource_access.web-pgc.roles

When I use another claim of type 'array' without nested objects like "aud", it works ... But the values defined in aud are not really roles ...

[jwt_auth]
required_claims = exp, {iss, "http://localhost:9080/auth/realms/ovalt"}
roles_claim_name = aud

The token looks like this :

{
  "exp": 1653424065,
  "iat": 1653388086,
  "auth_time": 1653388065,
  "jti": "e1689137-43e7-47f2-a87f-c2111ea30f7d",
  "iss": "http://localhost:9080/auth/realms/ovalt",
  "aud": [
    "web-pgc",
    "account"
  ],
  "sub": "df053ed9-a9ce-41bf-9143-85e75ee2657c",
  "typ": "Bearer",
  "azp": "web-cockpit",
  "nonce": "f122afad-f839-4f11-89ba-84cd9cb57802",
  "session_state": "606b3c5c-eb5c-4a29-814e-1e964104db3e",
  "acr": "0",
  "allowed-origins": [
    "http://localhost:8080",
    "http://localhost:3000"
  ],
  "realm_access": {
    "roles": [
      "default-roles-ovalt",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "web-pgc": {
      "roles": [
        "pgc_user"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email profile",
  "sid": "606b3c5c-eb5c-4a29-814e-1e964104db3e",
  "email_verified": false,
  "preferred_username": "testuser"
}

meveno avatar May 25 '22 07:05 meveno

@meveno Nested JWT-Tokens isn't pissible with roles_claim_name. A new parameter roles_claim_path is fixing this issue. You can use it, if you build couchdb from source or wait for the next release.

big-r81 avatar Oct 27 '22 11:10 big-r81