Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow configurability of JWT claims that require a value #3165

Merged
merged 1 commit into from Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion rel/overlay/etc/default.ini
Expand Up @@ -145,7 +145,9 @@ max_db_number_for_dbs_info_req = 100

;[jwt_auth]
; List of claims to validate
; required_claims =
; can be the name of a claim like "exp" or a tuple if the claim requires
; a parameter
; required_claims = exp, {iss, "IssuerNameHere"}
;
; [jwt_keys]
; Configure at least one key here if using the JWT auth handler.
Expand Down
2 changes: 2 additions & 0 deletions src/couch/src/couch_httpd.erl
Expand Up @@ -931,6 +931,8 @@ error_info({error, {illegal_database_name, Name}}) ->
{400, <<"illegal_database_name">>, Message};
error_info({missing_stub, Reason}) ->
{412, <<"missing_stub">>, Reason};
error_info({misconfigured_server, Reason}) ->
{500, <<"misconfigured_server">>, couch_util:to_binary(Reason)};
error_info({Error, Reason}) ->
{500, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
error_info(Error) ->
Expand Down
19 changes: 14 additions & 5 deletions src/couch/src/couch_httpd_auth.erl
Expand Up @@ -209,13 +209,22 @@ jwt_authentication_handler(Req) ->

get_configured_claims() ->
Claims = config:get("jwt_auth", "required_claims", ""),
case re:split(Claims, "\s*,\s*", [{return, list}]) of
[[]] ->
[]; %% if required_claims is the empty string.
List ->
[list_to_existing_atom(C) || C <- List]
Re = "((?<key1>[a-z]+)|{(?<key2>[a-z]+)\s*,\s*\"(?<val>[^\"]+)\"})",
case re:run(Claims, Re, [global, {capture, [key1, key2, val], binary}]) of
nomatch when Claims /= "" ->
couch_log:error("[jwt_auth] required_claims is set to an invalid value.", []),
throw({misconfigured_server, <<"JWT is not configured correctly">>});
nomatch ->
[];
{match, Matches} ->
lists:map(fun to_claim/1, Matches)
end.

to_claim([Key, <<>>, <<>>]) ->
binary_to_atom(Key, latin1);
to_claim([<<>>, Key, Value]) ->
{binary_to_atom(Key, latin1), Value}.

cookie_authentication_handler(Req) ->
cookie_authentication_handler(Req, couch_auth_cache).

Expand Down
77 changes: 77 additions & 0 deletions test/elixir/test/jwtauth_test.exs
Expand Up @@ -136,4 +136,81 @@ defmodule JwtAuthTest do
assert resp.body["userCtx"]["name"] == "adm"
assert resp.body["info"]["authenticated"] == "default"
end

test "jwt auth with required iss claim", _context do

secret = "zxczxc12zxczxc12"

server_config = [
%{
:section => "jwt_auth",
:key => "required_claims",
:value => "{iss, \"hello\"}"
},
%{
:section => "jwt_keys",
:key => "hmac:_default",
:value => :base64.encode(secret)
},
%{
:section => "jwt_auth",
:key => "allowed_algorithms",
:value => "HS256, HS384, HS512"
}
]

run_on_modified_server(server_config, fn -> good_iss("HS256", secret) end)
run_on_modified_server(server_config, fn -> bad_iss("HS256", secret) end)
end

def good_iss(alg, key) do
{:ok, token} = :jwtf.encode(
{
[
{"alg", alg},
{"typ", "JWT"}
]
},
{
[
{"iss", "hello"},
{"sub", "couch@apache.org"},
{"_couchdb.roles", ["testing"]
}
]
}, key)

resp = Couch.get("/_session",
headers: [authorization: "Bearer #{token}"]
)

assert resp.body["userCtx"]["name"] == "couch@apache.org"
assert resp.body["userCtx"]["roles"] == ["testing"]
assert resp.body["info"]["authenticated"] == "jwt"
end

def bad_iss(alg, key) do
{:ok, token} = :jwtf.encode(
{
[
{"alg", alg},
{"typ", "JWT"}
]
},
{
[
{"iss", "goodbye"},
{"sub", "couch@apache.org"},
{"_couchdb.roles", ["testing"]
}
]
}, key)

resp = Couch.get("/_session",
headers: [authorization: "Bearer #{token}"]
)

assert resp.status_code == 400
end

end