pow-auth / assent

Multi-provider framework in Elixir
https://powauth.com
MIT License
391 stars 45 forks source link

Google Sign in for mobile strategy #89

Open sezaru opened 3 years ago

sezaru commented 3 years ago

Currently, assent has support for Google Sign in for the web, the client sends the code and scope and the strategy retrieves from google the JWT with the data.

This works fine for the web, but not so much for mobile apps where normally the client will retrieve the JWT (id token) directly and the server job is to validate it.

Since I need exactly this case, I created a new strategy for it, you can add it to Assent if you want, and also I would appreciate it if you can take a look and see if there is some flaw in the logic.

The strategy itself is very simple, you simply send the id_token to id and it will validate it.

defmodule Core.Identity.GoogleSignIn.Strategy do
  @moduledoc false

  alias Core.Identity.GoogleSignIn.JWTManager

  @behaviour Assent.Strategy

  @impl true
  def callback(_, params) do
    %{id_token: id_token} = params

    case JWTManager.verify_and_validate(id_token) do
      {:ok, user} -> {:ok, %{user: user}}
      {:error, _} -> {:error, :invalid_token}
    end
  end

  @impl true
  def authorize_url(_), do: throw("not implemented")
end

For validation, here are my modules:

defmodule Core.Identity.GoogleSignIn.JWTVerifyHook do
  @moduledoc false

  use Joken.Hooks

  @impl true
  def before_verify(_, {jwt, %Joken.Signer{}}) do
    with {:ok, %{"kid" => kid}} <- Joken.peek_header(jwt),
         {:ok, algorithm, key} <- GoogleCerts.fetch(kid) do
      {:cont, {jwt, Joken.Signer.create(algorithm, key)}}
    else
      _ -> {:halt, {:error, :no_signer}}
    end
  end
end
defmodule Core.Identity.GoogleSignIn.JWTManager do
  @moduledoc false

  alias Core.Identity.GoogleSignIn.JWTVerifyHook

  use Joken.Config, default_signer: nil

  add_hook(JWTVerifyHook)

  @iss "https://accounts.google.com"

  @impl true
  def token_config do
    default_claims(skip: [:aud, :iss])
    |> add_claim("iss", nil, fn iss -> iss == @iss end)
    |> add_claim("aud", nil, fn aud -> aud == aud() end)
  end

  defp aud do
    Application.fetch_env!(:core, :pow_assent)
       |> Keyword.fetch!(:providers)
       |> Keyword.fetch!(:google_sign_in)
       |> Keyword.fetch!(:client_id)
  end
end