From 9ceba2be979d8b6469df87d2b38b90fda33bd2e7 Mon Sep 17 00:00:00 2001 From: Justin Smestad Date: Wed, 27 Apr 2022 13:12:13 -0600 Subject: [PATCH 1/5] Support verifying cookies from Firebase --- CHANGELOG.md | 4 +++ README.md | 12 ++++++- lib/mock.ex | 36 +++++++++++++++++++++ lib/source/google_key_source.ex | 26 +++++++++++----- lib/token.ex | 36 +++++++++++++++++++-- mix.exs | 6 ++-- test/token_test.exs | 55 +++++++++++++++++++++++++++++++++ 7 files changed, 161 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2dfbd..a293e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Added support for verifying session cookies. + ## 0.5.1 - Set default expiry on mocked token to 1 hour from utc now. diff --git a/README.md b/README.md index d1cb7bf..b3493ec 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,10 @@ end Add the Firebase auth issuer name for your project to your `config.exs`. This is required to make sure only your project's firebase tokens are accepted. ```elixir -config :ex_firebase_auth, :issuer, "https://securetoken.google.com/project-123abc" +config :ex_firebase_auth, + issuer: "https://securetoken.google.com/project-123abc", + # See https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects/createSessionCookie + cookie_issuer: "https://session.firebase.google.com/project-123abc" ``` Verifying a token @@ -42,6 +45,13 @@ ExFirebaseAuth.Token.verify_token("Some token string") iex> {:ok, "userid", %{}} ``` +Verifying a cookie + +```elixir +ExFirebaseAuth.Token.verify_cookie("Some token string") +iex> {:ok, "userid", %{}} +``` + Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at [https://hexdocs.pm/ex_firebase_auth](https://hexdocs.pm/ex_firebase_auth). diff --git a/lib/mock.ex b/lib/mock.ex index ceb8f8f..4249b78 100644 --- a/lib/mock.ex +++ b/lib/mock.ex @@ -87,6 +87,42 @@ defmodule ExFirebaseAuth.Mock do payload end + @spec generate_cookie(String.t(), map) :: String.t() + @doc ~S""" + Generates a firebase-like session cookie token with the mock's private key. Will raise when mock is not enabled. + + ## Examples + + iex> ExFirebaseAuth.Mock.generate_cookie("userid", %{"claim" => "value"}) + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJqb2UifQ.shLcxOl_HBBsOTvPnskfIlxHUibPN7Y9T4LhPB-iBwM" + """ + def generate_cookie(sub, claims \\ %{}) do + unless is_enabled?() do + raise "Cannot generate mocked token, because ExFirebaseAuth.Mock is not enabled in your config." + end + + {kid, jwk} = get_private_key() + + jws = %{ + "alg" => "RS256", + "kid" => kid + } + + # Put exp claim, unless previously specified in claims + exp = DateTime.utc_now() |> DateTime.add(3600, :second) |> DateTime.to_unix() + claims = Map.put_new(claims, "exp", exp) + + jwt = + Map.merge(claims, %{ + "iss" => ExFirebaseAuth.Token.cookie_issuer(), + "sub" => sub + }) + + {_, payload} = JOSE.JWT.sign(jwk, jws, jwt) |> JOSE.JWS.compact() + + payload + end + defp mock_config, do: Application.get_env(:ex_firebase_auth, :mock, []) defp find_or_create_private_key_table do diff --git a/lib/source/google_key_source.ex b/lib/source/google_key_source.ex index c1552c0..791a0ad 100644 --- a/lib/source/google_key_source.ex +++ b/lib/source/google_key_source.ex @@ -1,18 +1,30 @@ defmodule ExFirebaseAuth.KeySource.Google do @moduledoc false - @endpoint_url "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com" + @endpoint_urls [ + "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com", + "https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys" + ] @behaviour ExFirebaseAuth.KeySource def fetch_certificates do - with {:ok, %Finch.Response{body: body}} <- - Finch.build(:get, @endpoint_url) |> Finch.request(ExFirebaseAuthFinch), - {:ok, json_data} <- Jason.decode(body) do - {:ok, convert_to_jose_keys(json_data)} + results = + @endpoint_urls + |> Enum.map(fn endpoint_url -> + with {:ok, %Finch.Response{body: body}} <- + Finch.build(:get, endpoint_url) |> Finch.request(ExFirebaseAuthFinch), + {:ok, json_data} <- Jason.decode(body) do + {:ok, convert_to_jose_keys(json_data)} + else + _ -> :error + end + end) + + if Enum.any?(results, &(&1 == :error)) do + :error else - _ -> - :error + {:ok, Enum.reduce(results, %{}, fn {:ok, result}, acc -> Enum.into(result, acc) end)} end end diff --git a/lib/token.ex b/lib/token.ex index 76e9576..dbd28a6 100644 --- a/lib/token.ex +++ b/lib/token.ex @@ -20,10 +20,21 @@ defmodule ExFirebaseAuth.Token do """ def issuer, do: Application.fetch_env!(:ex_firebase_auth, :issuer) + @spec cookie_issuer :: String.t() + @doc ~S""" + Returns the configured issuer + + ## Examples + + iex> ExFirebaseAuth.Token.issuer() + "https://session.firebase.google.com/project-123abc" + """ + def cookie_issuer, do: Application.fetch_env!(:ex_firebase_auth, :cookie_issuer) + @spec verify_token(String.t()) :: {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} @doc ~S""" - Verifies a token agains google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. + Verifies a token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. ## Examples @@ -34,14 +45,33 @@ defmodule ExFirebaseAuth.Token do {:error, "Invalid JWT header, `kid` missing"} """ def verify_token(token_string) do - issuer = issuer() + do_verify_token(token_string, issuer()) + end + + @spec verify_cookie(String.t()) :: + {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} + @doc ~S""" + Verifies a cookie token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. + + ## Examples + + iex> ExFirebaseAuth.Token.verify_cookie("ey.some.token") + {:ok, "user id", %{}} + + iex> ExFirebaseAuth.Token.verify_cookie("ey.some.token") + {:error, "Invalid JWT header, `kid` missing"} + """ + def verify_cookie(cookie_string) do + do_verify_token(cookie_string, cookie_issuer()) + end + defp do_verify_token(token_string, issuer) do with {:jwtheader, %{fields: %{"kid" => kid}}} <- peek_token_kid(token_string), # read key from store {:key, %JOSE.JWK{} = key} <- {:key, get_public_key(kid)}, # check if verify returns true and issuer matches {:verify, {true, %{fields: %{"iss" => ^issuer, "sub" => sub, "exp" => exp}} = data, _}} <- - {:verify, JOSE.JWT.verify(key, token_string)}, + {:verify, JOSE.JWT.verify(key, token_string)} |> IO.inspect(), # Verify exp date {:verify, {:ok, _}} <- {:verify, verify_expiry(exp)} do {:ok, sub, data} diff --git a/mix.exs b/mix.exs index 2f875c0..ce83a68 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ExFirebaseAuth.MixProject do def project do [ app: :ex_firebase_auth, - version: "0.5.1", + version: "0.6.0", elixir: "~> 1.10", start_permanent: Mix.env() == :prod, deps: deps(), @@ -30,8 +30,8 @@ defmodule ExFirebaseAuth.MixProject do defp deps do [ {:jose, "~> 1.10"}, - {:finch, "~> 0.10.0"}, - {:jason, "~> 1.3.0"}, + {:finch, "~> 0.10"}, + {:jason, "~> 1.3"}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} ] end diff --git a/test/token_test.exs b/test/token_test.exs index 3515ed4..b74735f 100644 --- a/test/token_test.exs +++ b/test/token_test.exs @@ -24,6 +24,61 @@ defmodule ExFirebaseAuth.TokenTest do end) end + describe "Token.verify_cookie/1" do + test "Does succeed on correct token" do + issuer = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, issuer) + + sub = Enum.random(?a..?z) + time_in_future = DateTime.utc_now() |> DateTime.add(360, :second) |> DateTime.to_unix() + claims = %{"exp" => time_in_future} + valid_token = Mock.generate_cookie(sub, claims) + assert {:ok, ^sub, jwt} = Token.verify_cookie(valid_token) + + %JOSE.JWT{ + fields: %{ + "iss" => iss_claim, + "sub" => sub_claim + } + } = jwt + + assert sub_claim == sub + assert iss_claim == issuer + end + + test "Does raise on no issuer being set" do + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + valid_token = Mock.generate_cookie("subsub") + Application.delete_env(:ex_firebase_auth, :cookie_issuer) + + assert_raise( + ArgumentError, + ~r/^could not fetch application environment :cookie_issuer for application :ex_firebase_auth because configuration at :cookie_issuer was not set/, + fn -> + Token.verify_cookie(valid_token) + end + ) + end + + test "Does fail on no `kid` being set in JWT header" do + sub = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + token = + generate_token( + %{ + "sub" => sub, + "iss" => "issuer" + }, + %{ + "alg" => "RS256" + } + ) + + assert {:error, "Invalid JWT header, `kid` missing"} = Token.verify_cookie(token) + end + end + describe "Token.verify_token/1" do test "Does succeed on correct token" do issuer = Enum.random(?a..?z) From d79136068b4db2138de25cd6cfe0ec9f2061c47e Mon Sep 17 00:00:00 2001 From: Justin Smestad Date: Wed, 27 Apr 2022 13:17:39 -0600 Subject: [PATCH 2/5] Remove IO.inspect/0 call --- lib/token.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/token.ex b/lib/token.ex index dbd28a6..47d1820 100644 --- a/lib/token.ex +++ b/lib/token.ex @@ -71,7 +71,7 @@ defmodule ExFirebaseAuth.Token do {:key, %JOSE.JWK{} = key} <- {:key, get_public_key(kid)}, # check if verify returns true and issuer matches {:verify, {true, %{fields: %{"iss" => ^issuer, "sub" => sub, "exp" => exp}} = data, _}} <- - {:verify, JOSE.JWT.verify(key, token_string)} |> IO.inspect(), + {:verify, JOSE.JWT.verify(key, token_string)}, # Verify exp date {:verify, {:ok, _}} <- {:verify, verify_expiry(exp)} do {:ok, sub, data} From 5eedf38c71269f9b6171f308f66b994bbf590623 Mon Sep 17 00:00:00 2001 From: Justin Smestad Date: Tue, 3 May 2022 12:51:41 -0600 Subject: [PATCH 3/5] Change implementation to ExFirebaseAuth.Cookie --- CHANGELOG.md | 2 +- lib/cookie.ex | 28 +++++++ lib/mock.ex | 2 +- lib/token.ex | 32 +------- test/cookie_test.exs | 170 +++++++++++++++++++++++++++++++++++++++++++ test/token_test.exs | 55 -------------- 6 files changed, 202 insertions(+), 87 deletions(-) create mode 100644 lib/cookie.ex create mode 100644 test/cookie_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index a293e11..e2bce16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- Added support for verifying session cookies. +- Added `ExFirebaseAuth.Cookie` to support for verifying session cookies. ## 0.5.1 diff --git a/lib/cookie.ex b/lib/cookie.ex new file mode 100644 index 0000000..9e76ef8 --- /dev/null +++ b/lib/cookie.ex @@ -0,0 +1,28 @@ +defmodule ExFirebaseAuth.Cookie do + @doc ~S""" + Returns the configured issuer + + ## Examples + + iex> ExFirebaseAuth.Token.issuer() + "https://session.firebase.google.com/project-123abc" + """ + def issuer, do: Application.fetch_env!(:ex_firebase_auth, :cookie_issuer) + + @spec verify(String.t()) :: + {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} + @doc ~S""" + Verifies a cookie token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. + + ## Examples + + iex> ExFirebaseAuth.Cookie.verify("ey.some.token") + {:ok, "user id", %{}} + + iex> ExFirebaseAuth.Cookie.verify("ey.some.token") + {:error, "Invalid JWT header, `kid` missing"} + """ + def verify(cookie_string) do + ExFirebaseAuth.Token.verify_token(cookie_string, issuer()) + end +end diff --git a/lib/mock.ex b/lib/mock.ex index 4249b78..03ad886 100644 --- a/lib/mock.ex +++ b/lib/mock.ex @@ -114,7 +114,7 @@ defmodule ExFirebaseAuth.Mock do jwt = Map.merge(claims, %{ - "iss" => ExFirebaseAuth.Token.cookie_issuer(), + "iss" => ExFirebaseAuth.Cookie.issuer(), "sub" => sub }) diff --git a/lib/token.ex b/lib/token.ex index 47d1820..7df6f0a 100644 --- a/lib/token.ex +++ b/lib/token.ex @@ -20,17 +20,6 @@ defmodule ExFirebaseAuth.Token do """ def issuer, do: Application.fetch_env!(:ex_firebase_auth, :issuer) - @spec cookie_issuer :: String.t() - @doc ~S""" - Returns the configured issuer - - ## Examples - - iex> ExFirebaseAuth.Token.issuer() - "https://session.firebase.google.com/project-123abc" - """ - def cookie_issuer, do: Application.fetch_env!(:ex_firebase_auth, :cookie_issuer) - @spec verify_token(String.t()) :: {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} @doc ~S""" @@ -45,27 +34,10 @@ defmodule ExFirebaseAuth.Token do {:error, "Invalid JWT header, `kid` missing"} """ def verify_token(token_string) do - do_verify_token(token_string, issuer()) - end - - @spec verify_cookie(String.t()) :: - {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} - @doc ~S""" - Verifies a cookie token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. - - ## Examples - - iex> ExFirebaseAuth.Token.verify_cookie("ey.some.token") - {:ok, "user id", %{}} - - iex> ExFirebaseAuth.Token.verify_cookie("ey.some.token") - {:error, "Invalid JWT header, `kid` missing"} - """ - def verify_cookie(cookie_string) do - do_verify_token(cookie_string, cookie_issuer()) + verify_token(token_string, issuer()) end - defp do_verify_token(token_string, issuer) do + def verify_token(token_string, issuer) do with {:jwtheader, %{fields: %{"kid" => kid}}} <- peek_token_kid(token_string), # read key from store {:key, %JOSE.JWK{} = key} <- {:key, get_public_key(kid)}, diff --git a/test/cookie_test.exs b/test/cookie_test.exs new file mode 100644 index 0000000..df57fb2 --- /dev/null +++ b/test/cookie_test.exs @@ -0,0 +1,170 @@ +defmodule ExFirebaseAuth.CookieTest do + use ExUnit.Case + + alias ExFirebaseAuth.{ + Cookie, + Mock + } + + defp generate_cookie(claims, jws) do + [{_kid, jwk}] = :ets.lookup(ExFirebaseAuth.Mock, :ets.first(ExFirebaseAuth.Mock)) + + {_, payload} = JOSE.JWT.sign(jwk, jws, claims) |> JOSE.JWS.compact() + + payload + end + + setup do + Application.put_env(:ex_firebase_auth, :mock, enabled: true) + Mock.generate_and_store_key_pair() + + on_exit(fn -> + :ok = Application.delete_env(:ex_firebase_auth, :mock) + :ok = Application.delete_env(:ex_firebase_auth, :cookie_issuer) + end) + end + + describe "Cookie.verify/1" do + test "Does succeed on correct token" do + issuer = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, issuer) + + sub = Enum.random(?a..?z) + time_in_future = DateTime.utc_now() |> DateTime.add(360, :second) |> DateTime.to_unix() + claims = %{"exp" => time_in_future} + valid_token = Mock.generate_cookie(sub, claims) + assert {:ok, ^sub, jwt} = Cookie.verify(valid_token) + + %JOSE.JWT{ + fields: %{ + "iss" => iss_claim, + "sub" => sub_claim + } + } = jwt + + assert sub_claim == sub + assert iss_claim == issuer + end + + test "Does raise on no issuer being set" do + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + valid_token = Mock.generate_cookie("subsub") + Application.delete_env(:ex_firebase_auth, :cookie_issuer) + + assert_raise( + ArgumentError, + ~r/^could not fetch application environment :cookie_issuer for application :ex_firebase_auth because configuration at :cookie_issuer was not set/, + fn -> + Cookie.verify(valid_token) + end + ) + end + + test "Does fail on no `kid` being set in JWT header" do + sub = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + token = + generate_cookie( + %{ + "sub" => sub, + "iss" => "issuer" + }, + %{ + "alg" => "RS256" + } + ) + + assert {:error, "Invalid JWT header, `kid` missing"} = Cookie.verify(token) + end + end + + test "Does fail invalid kid being set" do + sub = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + token = + generate_cookie( + %{ + "sub" => sub, + "iss" => "issuer" + }, + %{ + "alg" => "RS256", + "kid" => "bogusbogus" + } + ) + + assert {:error, "Public key retrieved from google was not found or could not be parsed"} = + Cookie.verify(token) + end + + test "Does fail on invalid signature with non-matching kid" do + sub = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + {_invalid_kid, public_key, private_key} = Mock.generate_key() + + _invalid_kid = JOSE.JWK.thumbprint(:md5, public_key) + [{valid_kid, _}] = :ets.lookup(ExFirebaseAuth.Mock, :ets.first(ExFirebaseAuth.Mock)) + + {_, token} = + JOSE.JWT.sign( + private_key, + %{ + "alg" => "RS256", + "kid" => valid_kid + }, + %{ + "sub" => sub, + "iss" => "issuer" + } + ) + |> JOSE.JWS.compact() + + assert {:error, "Invalid signature"} = Cookie.verify(token) + end + + test "Does fail on invalid issuer" do + sub = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + [{kid, _}] = :ets.lookup(ExFirebaseAuth.Mock, :ets.first(ExFirebaseAuth.Mock)) + + token = + generate_cookie( + %{ + "sub" => sub, + "iss" => "bogusissuer" + }, + %{ + "alg" => "RS256", + "kid" => kid + } + ) + + assert {:error, "Signed by invalid issuer"} = Cookie.verify(token) + end + + test "Does fail on invalid JWT with raised exception handled" do + Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") + + invalid_token = "invalid.jwt.token" + + assert {:error, "Invalid JWT"} = Cookie.verify(invalid_token) + end + + test "Does fail on expired JWT" do + issuer = Enum.random(?a..?z) + Application.put_env(:ex_firebase_auth, :cookie_issuer, issuer) + + sub = Enum.random(?a..?z) + + time_in_past = DateTime.utc_now() |> DateTime.add(-60, :second) |> DateTime.to_unix() + claims = %{"exp" => time_in_past} + + valid_token = Mock.generate_cookie(sub, claims) + + assert {:error, "Expired JWT"} = Cookie.verify(valid_token) + end +end diff --git a/test/token_test.exs b/test/token_test.exs index b74735f..3515ed4 100644 --- a/test/token_test.exs +++ b/test/token_test.exs @@ -24,61 +24,6 @@ defmodule ExFirebaseAuth.TokenTest do end) end - describe "Token.verify_cookie/1" do - test "Does succeed on correct token" do - issuer = Enum.random(?a..?z) - Application.put_env(:ex_firebase_auth, :cookie_issuer, issuer) - - sub = Enum.random(?a..?z) - time_in_future = DateTime.utc_now() |> DateTime.add(360, :second) |> DateTime.to_unix() - claims = %{"exp" => time_in_future} - valid_token = Mock.generate_cookie(sub, claims) - assert {:ok, ^sub, jwt} = Token.verify_cookie(valid_token) - - %JOSE.JWT{ - fields: %{ - "iss" => iss_claim, - "sub" => sub_claim - } - } = jwt - - assert sub_claim == sub - assert iss_claim == issuer - end - - test "Does raise on no issuer being set" do - Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") - valid_token = Mock.generate_cookie("subsub") - Application.delete_env(:ex_firebase_auth, :cookie_issuer) - - assert_raise( - ArgumentError, - ~r/^could not fetch application environment :cookie_issuer for application :ex_firebase_auth because configuration at :cookie_issuer was not set/, - fn -> - Token.verify_cookie(valid_token) - end - ) - end - - test "Does fail on no `kid` being set in JWT header" do - sub = Enum.random(?a..?z) - Application.put_env(:ex_firebase_auth, :cookie_issuer, "issuer") - - token = - generate_token( - %{ - "sub" => sub, - "iss" => "issuer" - }, - %{ - "alg" => "RS256" - } - ) - - assert {:error, "Invalid JWT header, `kid` missing"} = Token.verify_cookie(token) - end - end - describe "Token.verify_token/1" do test "Does succeed on correct token" do issuer = Enum.random(?a..?z) From 4a981ee10ccc92b9e50ed465d600c198642468c5 Mon Sep 17 00:00:00 2001 From: Justin Smestad Date: Tue, 3 May 2022 12:53:22 -0600 Subject: [PATCH 4/5] Match verify_token and use verify_cookie --- README.md | 2 +- lib/cookie.ex | 8 ++++---- test/cookie_test.exs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b3493ec..9d5ee23 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ iex> {:ok, "userid", %{}} Verifying a cookie ```elixir -ExFirebaseAuth.Token.verify_cookie("Some token string") +ExFirebaseAuth.Cookie.verify_cookie("Some token string") iex> {:ok, "userid", %{}} ``` diff --git a/lib/cookie.ex b/lib/cookie.ex index 9e76ef8..2548f75 100644 --- a/lib/cookie.ex +++ b/lib/cookie.ex @@ -9,20 +9,20 @@ defmodule ExFirebaseAuth.Cookie do """ def issuer, do: Application.fetch_env!(:ex_firebase_auth, :cookie_issuer) - @spec verify(String.t()) :: + @spec verify_cookie(String.t()) :: {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} @doc ~S""" Verifies a cookie token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. ## Examples - iex> ExFirebaseAuth.Cookie.verify("ey.some.token") + iex> ExFirebaseAuth.Cookie.verify_cookie("ey.some.token") {:ok, "user id", %{}} - iex> ExFirebaseAuth.Cookie.verify("ey.some.token") + iex> ExFirebaseAuth.Cookie.verify_cookie("ey.some.token") {:error, "Invalid JWT header, `kid` missing"} """ - def verify(cookie_string) do + def verify_cookie(cookie_string) do ExFirebaseAuth.Token.verify_token(cookie_string, issuer()) end end diff --git a/test/cookie_test.exs b/test/cookie_test.exs index df57fb2..e5a7cf9 100644 --- a/test/cookie_test.exs +++ b/test/cookie_test.exs @@ -24,7 +24,7 @@ defmodule ExFirebaseAuth.CookieTest do end) end - describe "Cookie.verify/1" do + describe "Cookie.verify_cookie/1" do test "Does succeed on correct token" do issuer = Enum.random(?a..?z) Application.put_env(:ex_firebase_auth, :cookie_issuer, issuer) @@ -33,7 +33,7 @@ defmodule ExFirebaseAuth.CookieTest do time_in_future = DateTime.utc_now() |> DateTime.add(360, :second) |> DateTime.to_unix() claims = %{"exp" => time_in_future} valid_token = Mock.generate_cookie(sub, claims) - assert {:ok, ^sub, jwt} = Cookie.verify(valid_token) + assert {:ok, ^sub, jwt} = Cookie.verify_cookie(valid_token) %JOSE.JWT{ fields: %{ @@ -55,7 +55,7 @@ defmodule ExFirebaseAuth.CookieTest do ArgumentError, ~r/^could not fetch application environment :cookie_issuer for application :ex_firebase_auth because configuration at :cookie_issuer was not set/, fn -> - Cookie.verify(valid_token) + Cookie.verify_cookie(valid_token) end ) end @@ -75,7 +75,7 @@ defmodule ExFirebaseAuth.CookieTest do } ) - assert {:error, "Invalid JWT header, `kid` missing"} = Cookie.verify(token) + assert {:error, "Invalid JWT header, `kid` missing"} = Cookie.verify_cookie(token) end end @@ -96,7 +96,7 @@ defmodule ExFirebaseAuth.CookieTest do ) assert {:error, "Public key retrieved from google was not found or could not be parsed"} = - Cookie.verify(token) + Cookie.verify_cookie(token) end test "Does fail on invalid signature with non-matching kid" do @@ -122,7 +122,7 @@ defmodule ExFirebaseAuth.CookieTest do ) |> JOSE.JWS.compact() - assert {:error, "Invalid signature"} = Cookie.verify(token) + assert {:error, "Invalid signature"} = Cookie.verify_cookie(token) end test "Does fail on invalid issuer" do @@ -143,7 +143,7 @@ defmodule ExFirebaseAuth.CookieTest do } ) - assert {:error, "Signed by invalid issuer"} = Cookie.verify(token) + assert {:error, "Signed by invalid issuer"} = Cookie.verify_cookie(token) end test "Does fail on invalid JWT with raised exception handled" do @@ -151,7 +151,7 @@ defmodule ExFirebaseAuth.CookieTest do invalid_token = "invalid.jwt.token" - assert {:error, "Invalid JWT"} = Cookie.verify(invalid_token) + assert {:error, "Invalid JWT"} = Cookie.verify_cookie(invalid_token) end test "Does fail on expired JWT" do @@ -165,6 +165,6 @@ defmodule ExFirebaseAuth.CookieTest do valid_token = Mock.generate_cookie(sub, claims) - assert {:error, "Expired JWT"} = Cookie.verify(valid_token) + assert {:error, "Expired JWT"} = Cookie.verify_cookie(valid_token) end end From 249e06f88c5063de1d32be0e9b18e2f97363534e Mon Sep 17 00:00:00 2001 From: Justin Smestad Date: Tue, 18 Oct 2022 08:26:21 -0600 Subject: [PATCH 5/5] Update lib/token.ex Co-authored-by: Robert Parcus <923630+rparcus@users.noreply.github.com> --- lib/token.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/token.ex b/lib/token.ex index 7df6f0a..7944e90 100644 --- a/lib/token.ex +++ b/lib/token.ex @@ -23,7 +23,7 @@ defmodule ExFirebaseAuth.Token do @spec verify_token(String.t()) :: {:error, String.t()} | {:ok, String.t(), JOSE.JWT.t()} @doc ~S""" - Verifies a token agains Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. + Verifies a token against Google's public keys. Returns {:ok, user_id, claims} if successful. {:error, _} otherwise. ## Examples