Skip to content

Commit d908e39

Browse files
authored
chore(401-when-jwt-token-is-missing): Returns 401 when jwt is missing or not parsable (#45)
1 parent 1423367 commit d908e39

File tree

6 files changed

+52
-20
lines changed

6 files changed

+52
-20
lines changed

neurow/lib/neurow/internal_api/endpoint.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ defmodule Neurow.InternalApi.Endpoint do
1616
&Neurow.Configuration.internal_api_verbose_authentication_errors/0,
1717
max_lifetime: &Neurow.Configuration.internal_api_jwt_max_lifetime/0,
1818
send_forbidden: &Neurow.InternalApi.Endpoint.send_forbidden/3,
19+
send_unauthorized: &Neurow.InternalApi.Endpoint.send_unauthorized/3,
1920
inc_error_callback: &Neurow.Observability.SecurityStats.inc_jwt_errors_internal/0,
2021
exclude_path_prefixes: [
2122
"/ping",
@@ -165,6 +166,10 @@ defmodule Neurow.InternalApi.Endpoint do
165166
send_error(conn, error_code, error_message, :forbidden)
166167
end
167168

169+
def send_unauthorized(conn, error_code, error_message) do
170+
send_error(conn, error_code, error_message, :unauthorized)
171+
end
172+
168173
defp send_error(conn, error_code, error_message, status \\ :bad_request) do
169174
response =
170175
:jiffy.encode(%{

neurow/lib/neurow/jwt_auth_plug.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ defmodule Neurow.JwtAuthPlug do
1111
:max_lifetime,
1212
:inc_error_callback,
1313
:send_forbidden,
14+
:send_unauthorized,
1415
allowed_algorithm: "HS256",
1516
verbose_authentication_errors: false,
1617
exclude_path_prefixes: []
@@ -58,6 +59,11 @@ defmodule Neurow.JwtAuthPlug do
5859
) do
5960
conn |> assign(:jwt_payload, payload.fields)
6061
else
62+
{:error, error_code, message}
63+
when error_code in [:invalid_authorization_header, :invalid_jwt_token] ->
64+
options.inc_error_callback.()
65+
conn |> unauthorized(error_code, message, options)
66+
6167
{:error, code, message} ->
6268
options.inc_error_callback.()
6369
conn |> forbidden(code, message, options)
@@ -213,6 +219,19 @@ defmodule Neurow.JwtAuthPlug do
213219
end
214220
end
215221

222+
defp unauthorized(conn, error_code, error_message, options) do
223+
Logger.error(
224+
"JWT authentication error: #{error_code} - #{error_message}, path: '#{conn.request_path}'",
225+
category: "security",
226+
error_code: "jwt_authentication.#{error_code}",
227+
authorization_header: conn |> get_req_header("authorization") |> List.first(),
228+
trace_id: conn |> get_req_header("x-request-id") |> List.first(),
229+
client_ip: conn |> get_req_header("x-forwarded-for") |> List.first()
230+
)
231+
232+
options.send_unauthorized.(conn, error_code, error_message) |> halt
233+
end
234+
216235
defp forbidden(conn, error_code, error_message, options) do
217236
jwt_token =
218237
case conn |> jwt_token_from_request(options) do

neurow/lib/neurow/public_api/endpoint.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Neurow.PublicApi.Endpoint do
99
jwk_provider: &Neurow.Configuration.public_api_issuer_jwks/1,
1010
audience: &Neurow.Configuration.public_api_audience/0,
1111
send_forbidden: &Neurow.PublicApi.Endpoint.send_forbidden/3,
12+
send_unauthorized: &Neurow.PublicApi.Endpoint.send_unauthorized/3,
1213
verbose_authentication_errors:
1314
&Neurow.Configuration.public_api_verbose_authentication_errors/0,
1415
max_lifetime: &Neurow.Configuration.public_api_jwt_max_lifetime/0,
@@ -86,6 +87,10 @@ defmodule Neurow.PublicApi.Endpoint do
8687
send_http_error(conn, 403, error_code, error_message)
8788
end
8889

90+
def send_unauthorized(conn, error_code, error_message) do
91+
send_http_error(conn, 401, error_code, error_message)
92+
end
93+
8994
def preflight_request(conn, _opts) do
9095
case conn.method do
9196
"OPTIONS" ->

neurow/test/neurow/internal_api/endpoint_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule Neurow.InternalApi.EndpointTest do
2121
test "other routes requires a JWT sent in authorization header" do
2222
conn = conn(:get, "/foo")
2323
call = Neurow.InternalApi.Endpoint.call(conn, [])
24-
assert call.status == 403
24+
assert call.status == 401
2525

2626
conn =
2727
conn(:get, "/foo")
@@ -34,7 +34,7 @@ defmodule Neurow.InternalApi.EndpointTest do
3434
test "other routes requires a JWT sent in x-interservice-authorization header" do
3535
conn = conn(:get, "/foo")
3636
call = Neurow.InternalApi.Endpoint.call(conn, [])
37-
assert call.status == 403
37+
assert call.status == 401
3838

3939
conn =
4040
conn(:get, "/foo")
@@ -45,7 +45,7 @@ defmodule Neurow.InternalApi.EndpointTest do
4545
end
4646

4747
describe "POST /v1/subscribe" do
48-
test "returns a 403 if called without a JWT token" do
48+
test "returns a 401 if called without a JWT token" do
4949
body =
5050
:jiffy.encode(%{
5151
message: %{event: "event_foo", payload: "foo56"},
@@ -56,10 +56,10 @@ defmodule Neurow.InternalApi.EndpointTest do
5656
conn(:post, "/v1/publish", body)
5757

5858
call = Neurow.InternalApi.Endpoint.call(conn, [])
59-
assert call.status == 403
59+
assert call.status == 401
6060
end
6161

62-
test "returns a 403 if called with an invalid JWT token" do
62+
test "returns a 401 if called with an invalid JWT token" do
6363
body =
6464
:jiffy.encode(%{
6565
message: %{event: "event_foo", payload: "foo56", timestamp: 123_456},
@@ -71,7 +71,7 @@ defmodule Neurow.InternalApi.EndpointTest do
7171
|> put_req_header("authorization", "Basic dXNlcjpwYXNzd29yZA==")
7272

7373
call = Neurow.InternalApi.Endpoint.call(conn, [])
74-
assert call.status == 403
74+
assert call.status == 401
7575
end
7676

7777
test "returns a 400 error if the request payload is invalid" do

neurow/test/neurow/jwt_auth_plug_test.exs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ defmodule Neurow.JwtAuthPlugTest do
2424
send_forbidden: fn conn, error_code, error_message ->
2525
conn |> assign(:forbidden_error, {error_code, error_message})
2626
end,
27+
send_unauthorized: fn conn, error_code, error_message ->
28+
conn |> assign(:unauthorized_error, {error_code, error_message})
29+
end,
2730
jwk_provider: fn issuer ->
2831
case issuer do
2932
"issuer_1" -> [@issuer_1_jwk_1, @issuer_1_jwk_2]
@@ -82,7 +85,7 @@ defmodule Neurow.JwtAuthPlugTest do
8285
assert response.assigns[:jwt_payload] == nil
8386
end
8487

85-
test "don not provide details about authentication errors if verbose_authentication_errors is set to false",
88+
test "do not provide details about authentication errors if verbose_authentication_errors is set to false",
8689
%{
8790
default_opts: opts
8891
} do
@@ -94,8 +97,8 @@ defmodule Neurow.JwtAuthPlugTest do
9497

9598
assert response.halted
9699

97-
assert response.assigns[:forbidden_error] ==
98-
{:invalid_authentication_token, "Invalid authentication token"},
100+
assert response.assigns[:unauthorized_error] ==
101+
{:invalid_authorization_header, "Invalid authorization header"},
99102
"Error details"
100103
end
101104

@@ -106,7 +109,7 @@ defmodule Neurow.JwtAuthPlugTest do
106109
assert response.halted, "Response halted"
107110
assert response.halted
108111

109-
assert response.assigns[:forbidden_error] ==
112+
assert response.assigns[:unauthorized_error] ==
110113
{:invalid_authorization_header, "Invalid authorization header"},
111114
"Error details"
112115
end
@@ -122,7 +125,7 @@ defmodule Neurow.JwtAuthPlugTest do
122125

123126
assert response.halted
124127

125-
assert response.assigns[:forbidden_error] ==
128+
assert response.assigns[:unauthorized_error] ==
126129
{:invalid_authorization_header, "Invalid authorization header"},
127130
"Error details"
128131
end
@@ -138,7 +141,7 @@ defmodule Neurow.JwtAuthPlugTest do
138141

139142
assert response.halted
140143

141-
assert response.assigns[:forbidden_error] ==
144+
assert response.assigns[:unauthorized_error] ==
142145
{:invalid_jwt_token, "Invalid JWT token"},
143146
"Error details"
144147
end
@@ -220,7 +223,7 @@ defmodule Neurow.JwtAuthPlugTest do
220223

221224
assert response.halted
222225

223-
assert response.assigns[:forbidden_error] ==
226+
assert response.assigns[:unauthorized_error] ==
224227
{:invalid_jwt_token, "Invalid JWT token"},
225228
"Error details"
226229
end

neurow/test/neurow/public_api/endpoint_test.exs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ defmodule Neurow.PublicApi.EndpointTest do
1212
conn(:get, "/v1/subscribe")
1313

1414
call(Neurow.PublicApi.Endpoint, conn, fn ->
15-
assert_receive {:send_resp_status, 403}
15+
assert_receive {:send_resp_status, 401}
1616
assert_receive {:send_resp_body, body}
1717

1818
json_event = parse_sse_json_event(body)
1919

20-
assert json_event.event == "neurow_error_403"
20+
assert json_event.event == "neurow_error_401"
2121

2222
assert json_event.data ==
2323
%{
@@ -37,12 +37,12 @@ defmodule Neurow.PublicApi.EndpointTest do
3737
|> put_req_header("authorization", "Bearer bad_token")
3838

3939
call(Neurow.PublicApi.Endpoint, conn, fn ->
40-
assert_receive {:send_resp_status, 403}
40+
assert_receive {:send_resp_status, 401}
4141
assert_receive {:send_resp_body, body}
4242

4343
json_event = parse_sse_json_event(body)
4444

45-
assert json_event.event == "neurow_error_403"
45+
assert json_event.event == "neurow_error_401"
4646

4747
assert json_event.data == %{
4848
"errors" => [
@@ -464,17 +464,17 @@ defmodule Neurow.PublicApi.EndpointTest do
464464
:ok
465465
end
466466

467-
test "the authentication logic is applyed to urls prefixed by the context path" do
467+
test "the authentication logic is applied to urls prefixed by the context path" do
468468
conn =
469469
conn(:get, "/v1/subscribe")
470470

471471
call(Neurow.PublicApi.Endpoint, conn, fn ->
472-
assert_receive {:send_resp_status, 403}
472+
assert_receive {:send_resp_status, 401}
473473
assert_receive {:send_resp_body, body}
474474

475475
json_event = parse_sse_json_event(body)
476476

477-
assert json_event.event == "neurow_error_403"
477+
assert json_event.event == "neurow_error_401"
478478

479479
assert json_event.data ==
480480
%{

0 commit comments

Comments
 (0)