Skip to content

Commit

Permalink
Update JWT for hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
terry-xiaoyu committed Mar 16, 2019
1 parent 3b8b421 commit aa3a6ec
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 33 deletions.
6 changes: 6 additions & 0 deletions etc/emqx_auth_jwt.conf
Expand Up @@ -7,6 +7,12 @@
## Value: String
auth.jwt.secret = emqxsecret

## From where the JWT string can be got
##
## Value: username | password
## Default: password
auth.jwt.from = password

## RSA or ECDSA public key file.
##
## Value: File
Expand Down
5 changes: 5 additions & 0 deletions priv/emqx_auth_jwt.schema
Expand Up @@ -7,6 +7,11 @@
{datatype, string}
]}.

{mapping, "auth.jwt.from", "emqx_auth_jwt.from", [
{default, password},
{datatype, atom}
]}.

%% RSA or ECDSA public key file
{mapping, "auth.jwt.pubkey", "emqx_auth_jwt.pubkey", [
{datatype, string}
Expand Down
44 changes: 20 additions & 24 deletions src/emqx_auth_jwt.erl
Expand Up @@ -16,28 +16,26 @@

-include_lib("emqx/include/emqx.hrl").

-behaviour(emqx_auth_mod).

%% emqx_auth_mod callbacks
-export([init/1, check/3, description/0]).

%%--------------------------------------------------------------------
%% emqx_auth_mod Callbacks
%%--------------------------------------------------------------------
-export([init/1, check/2, description/0]).

init(Env) ->
{ok, Env}.

check(_Credentials, undefined, _Env) ->
{error, token_undefined};
check(_Credentials, Token, Env) ->
try jwerl:header(Token) of
Headers ->
verify_token(Headers, Token, Env)
catch
_Error:Reason ->
logger:error("JWT check error:~p", [Reason]),
ignore
check(Credentials, Env = #{from := From}) ->
case maps:find(From, Credentials) of
error -> {ok, Credentials#{result => token_undefined}};
{ok, Token} ->
try jwerl:header(Token) of
Headers ->
case verify_token(Headers, Token, Env) of
{ok, Claims} -> {stop, Credentials#{result => success, jwt_claims => Claims}};
{error, Reason} -> {stop, Credentials#{result => Reason}}
end
catch
_Error:Reason ->
logger:error("JWT check error:~p", [Reason]),
ok
end
end.

verify_token(#{alg := <<"HS", _/binary>>}, _Token, #{secret := undefined}) ->
Expand All @@ -58,15 +56,13 @@ verify_token(Header, _Token, _Env) ->

verify_token2(Alg, Token, SecretOrKey) ->
try jwerl:verify(Token, decode_algo(Alg), SecretOrKey) of
{ok, _Claims} ->
ok;
{ok, Claims} ->
{ok, Claims};
{error, Reason} ->
logger:error("JWT decode error:~p", [Reason]),
{error, token_error}
{error, Reason}
catch
_Error:Reason ->
logger:error("JWT decode error:~p", [Reason]),
{error, token_error}
{error, Reason}
end.

decode_algo(<<"HS256">>) -> hs256;
Expand Down
7 changes: 5 additions & 2 deletions src/emqx_auth_jwt_app.erl
Expand Up @@ -22,13 +22,15 @@

-define(APP, emqx_auth_jwt).

-define(JWT_ACTION, {emqx_auth_jwt, check, [auth_env()]}).

start(_Type, _Args) ->
emqx_access_control:register_mod(auth, ?APP, auth_env()),
emqx:hook('client.authenticate', ?JWT_ACTION),
emqx_auth_jwt_cfg:register(),
supervisor:start_link({local, ?MODULE}, ?MODULE, []).

stop(_State) ->
emqx_access_control:unregister_mod(auth, ?APP),
emqx:unhook('client.authenticate', ?JWT_ACTION),
emqx_auth_jwt_cfg:unregister().

%%--------------------------------------------------------------------
Expand All @@ -44,6 +46,7 @@ init([]) ->

auth_env() ->
#{secret => application:get_env(?APP, secret, undefined),
from => application:get_env(?APP, from, password),
pubkey => read_pubkey()}.

read_pubkey() ->
Expand Down
35 changes: 28 additions & 7 deletions test/emqx_auth_jwt_SUITE.erl
Expand Up @@ -33,6 +33,7 @@ groups() ->
init_per_suite(Config) ->
DataDir = proplists:get_value(data_dir, Config),
application:set_env(emqx_auth_jwt, secret, "emqxsecret"),
application:set_env(emqx_auth_jwt, from, password),
Apps = [start_apps(App, DataDir) || App <- [emqx, emqx_auth_jwt]],
ct:log("Apps: ~p~n", [Apps]),
Config.
Expand All @@ -46,20 +47,40 @@ check_auth(_) ->
{username, <<"plain">>},
{exp, os:system_time(seconds) + 1}], hs256, <<"emqxsecret">>),
ct:pal("Jwt: ~p~n", [Jwt]),
ok = emqx_access_control:authenticate(Plain, Jwt),

Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{result := success, jwt_claims := #{client_id := <<"client1">>}}}, Result0),

ct:sleep(1000),
{error,token_error} = emqx_access_control:authenticate(Plain, Jwt),
Result1 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result after 1000ms: ~p~n", [Result1]),
?assertMatch({error, _}, Result1),

Jwt_Error = jwerl:sign([{client_id, <<"client1">>},
{username, <<"plain">>}], hs256, <<"secret">>),
{error, token_error} = emqx_access_control:authenticate(Plain, Jwt_Error),
?assertEqual(case emqx_config:get_env(allow_anonymous, false) of
true -> ok;
false -> {error, auth_modules_not_found}
end, emqx_access_control:authenticate(Plain, <<"asd">>)).
ct:pal("invalid jwt: ~p~n", [Jwt_Error]),
Result2 = emqx_access_control:authenticate(Plain#{password => Jwt_Error}),
ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]),
?assertEqual({error, invalid_signature}, Result2),

?assertMatch({error, _}, emqx_access_control:authenticate(Plain#{password => <<"asd">>})).

get_base_dir() ->
{file, Here} = code:is_loaded(?MODULE),
filename:dirname(filename:dirname(Here)).

local_path(RelativePath) ->
filename:join([get_base_dir(), RelativePath]).

start_apps(App, _DataDir) ->
application:set_env(emqx, allow_anonymous, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, acl_file,
local_path("deps/emqx/test/emqx_SUITE_data/acl.conf")),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, plugins_loaded_file,
local_path("deps/emqx/test/emqx_SUITE_data/loaded_plugins")),
application:ensure_all_started(App).
%start_apps(App, DataDir) ->
% Schema = cuttlefish_schema:files([filename:join([DataDir, atom_to_list(App) ++ ".schema"])]),
Expand Down

0 comments on commit aa3a6ec

Please sign in to comment.