diff options
author | Alexander Trauzzi <acj@trauzzi.me> | 2020-03-19 05:43:47 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-19 10:43:47 +0000 |
commit | 032934f3764c9e1ae2f8f359cf039349bf56cf86 (patch) | |
tree | 12b10362c8ea32307ec208d30f0dc0978838a731 | |
parent | f7bdc8c085fcb3cf7c4155adcb97b5675b5e2467 (diff) | |
download | couchdb-032934f3764c9e1ae2f8f359cf039349bf56cf86.tar.gz |
Feature - Add JWT support (#2648)
Add JWT Authentication Handler
Co-authored-by: Robert Newson <rnewson@apache.org>
Co-authored-by: Joan Touzet <wohali@users.noreply.github.com>
-rw-r--r-- | rel/overlay/etc/default.ini | 10 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_auth.erl | 4 | ||||
-rw-r--r-- | src/couch/src/couch_httpd_auth.erl | 26 | ||||
-rw-r--r-- | test/elixir/test/config/test-config.ini | 2 | ||||
-rw-r--r-- | test/elixir/test/jwtauth_test.exs | 39 |
5 files changed, 80 insertions, 1 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 2676ef530..82a56590f 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -134,10 +134,20 @@ max_db_number_for_dbs_info_req = 100 ; authentication_handlers = {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} ; uncomment the next line to enable proxy authentication ; authentication_handlers = {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} +; uncomment the next line to enable JWT authentication +; authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} ; prevent non-admins from accessing /_all_dbs ; admin_only_all_dbs = true +;[jwt_auth] +; Symmetric secret to be used when checking JWT token signatures +; secret = +; List of claims to validate +; required_claims = exp +; List of algorithms to accept during checks +; allowed_algorithms = HS256 + [couch_peruser] ; If enabled, couch_peruser ensures that a private per-user database ; exists for each document in _users. These databases are writable only diff --git a/src/chttpd/src/chttpd_auth.erl b/src/chttpd/src/chttpd_auth.erl index 607f09a8a..1b6d16eb3 100644 --- a/src/chttpd/src/chttpd_auth.erl +++ b/src/chttpd/src/chttpd_auth.erl @@ -18,6 +18,7 @@ -export([default_authentication_handler/1]). -export([cookie_authentication_handler/1]). -export([proxy_authentication_handler/1]). +-export([jwt_authentication_handler/1]). -export([party_mode_handler/1]). -export([handle_session_req/1]). @@ -51,6 +52,9 @@ cookie_authentication_handler(Req) -> proxy_authentication_handler(Req) -> couch_httpd_auth:proxy_authentication_handler(Req). +jwt_authentication_handler(Req) -> + couch_httpd_auth:jwt_authentication_handler(Req). + party_mode_handler(#httpd{method='POST', path_parts=[<<"_session">>]} = Req) -> % See #1947 - users should always be able to attempt a login Req#httpd{user_ctx=#user_ctx{}}; diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl index 43ecda958..7c55f390e 100644 --- a/src/couch/src/couch_httpd_auth.erl +++ b/src/couch/src/couch_httpd_auth.erl @@ -31,6 +31,8 @@ -export([cookie_auth_cookie/4, cookie_scheme/1]). -export([maybe_value/3]). +-export([jwt_authentication_handler/1]). + -import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]). -compile({no_auto_import,[integer_to_binary/1, integer_to_binary/2]}). @@ -186,6 +188,30 @@ proxy_auth_user(Req) -> end end. +jwt_authentication_handler(Req) -> + case {config:get("jwt_auth", "secret"), header_value(Req, "Authorization")} of + {Secret, "Bearer " ++ Jwt} when Secret /= undefined -> + RequiredClaims = get_configured_claims(), + AllowedAlgorithms = get_configured_algorithms(), + case jwtf:decode(?l2b(Jwt), [{alg, AllowedAlgorithms} | RequiredClaims], fun(_,_) -> Secret end) 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 + }} + end; + {error, Reason} -> + throw({unauthorized, Reason}) + end; + {_, _} -> Req + end. + +get_configured_algorithms() -> + re:split(config:get("jwt_auth", "allowed_algorithms", "HS256"), "\s*,\s*", [{return, binary}]). + +get_configured_claims() -> + lists:usort(re:split(config:get("jwt_auth", "required_claims", ""), "\s*,\s*", [{return, binary}])). cookie_authentication_handler(Req) -> cookie_authentication_handler(Req, couch_auth_cache). diff --git a/test/elixir/test/config/test-config.ini b/test/elixir/test/config/test-config.ini index 72a13a707..1980139d1 100644 --- a/test/elixir/test/config/test-config.ini +++ b/test/elixir/test/config/test-config.ini @@ -1,2 +1,2 @@ [chttpd] -authentication_handlers = {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} +authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, proxy_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} diff --git a/test/elixir/test/jwtauth_test.exs b/test/elixir/test/jwtauth_test.exs new file mode 100644 index 000000000..2e78ee989 --- /dev/null +++ b/test/elixir/test/jwtauth_test.exs @@ -0,0 +1,39 @@ +defmodule JwtAuthTest do + use CouchTestCase + + @moduletag :authentication + + test "jwt auth with secret", _context do + + secret = "zxczxc12zxczxc12" + + server_config = [ + %{ + :section => "jwt_auth", + :key => "secret", + :value => secret + } + ] + + run_on_modified_server(server_config, fn -> + test_fun() + end) + end + + def test_fun() do + resp = Couch.get("/_session", + headers: [authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjb3VjaEBhcGFjaGUub3JnIn0.KYHmGXWj0HNHzZCjfOfsIfZWdguEBSn31jUdDUA9118"] + ) + + assert resp.body["userCtx"]["name"] == "couch@apache.org" + assert resp.body["info"]["authenticated"] == "jwt" + end + + test "jwt auth without secret", _context do + + resp = Couch.get("/_session") + + assert resp.body["userCtx"]["name"] == "adm" + assert resp.body["info"]["authenticated"] == "default" + end +end |