ueberauth / guardian

Elixir Authentication
MIT License
3.44k stars 381 forks source link

How to create custom tokens for Firebase? #459

Closed tsurupin closed 6 years ago

tsurupin commented 6 years ago

I'm trying to figure out how to create custom tokens with JWT for FIrebase. According to Firebase doc, we can create it in the following ways in ruby.

require "jwt"

# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."

def create_custom_token(uid, is_premium_account)
  now_seconds = Time.now.to_i
  payload = {:iss => $service_account_email,
             :sub => $service_account_email,
             :aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
             :iat => now_seconds,
             :exp => now_seconds+(60*60), # Maximum expiration time is one hour
             :uid => uid,
             :claims => {:premium_account => is_premium_account}}
  JWT.encode payload, $private_key, "RS256"
end

How can we create custom tokens with Guardian? If there is the doc for that, that would be very helpful.

yordis commented 6 years ago

@tsurupin could you just create your own token https://hexdocs.pm/guardian/Guardian.Token.html

You can follow the implementation of the current JWT one

@ueberauth/core what are your thoughts on this one?

tsurupin commented 6 years ago

@yordis Thanks. I could create the custom token. I paste code snippets.

  @aud "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
  @one_hour_in_unix 60 * 60
  def create_token(user) do
    current_time = DateTime.to_unix(DateTime.utc_now)
    one_hour_later = current_time + @one_hour_in_unix

    custom_claims = %{
      iss: System.get_env("FIREBASE_SERVICE_ACCOUNT_EMAIL"),
      sub: System.get_env("FIREBASE_SERVICE_ACCOUNT_EMAIL"),
      aud: @aud,
      iat: current_time,
      exp: one_hour_later,
      uid: user.uid
    }

    with {:ok, jwt, full_claims} <- Api.Guardian.encode_and_sign(user, custom_claims)
    do
      {:ok, user.uid, jwt}
    else
      {:error, reason} -> {:error, reason}
      _ -> {:error, "unknown"}
    end
  end

-confix.ex

config :api, Api.Guardian,
  allowed_algos: ["RS256"],
  verify_module: Api.Guardian.JWT,
  issuer: System.get_env("FIREBASE_SERVICE_ACCOUNT_EMAIL"),
  ttl: {1, :hours},
  secret_key: {Api.GuardianSecretKey, :get_key, []}

- guardian_secret_key.ex
defmodule Api.GuardianSecretKey do
  def get_key() do
    JOSE.JWK.from_pem_file(System.get_env("SECRET_PEM_FILE_PATH"))
  end
end

- guardian.ex
defmodule Api.Guardian do
  use Guardian, otp_app: :api

  def subject_for_token(resource, claims) do
    {:ok, claims["sub"]}
  end

  def subject_for_token(_, _) do
    {:error, :reason_for_error}
  end

  def resource_from_claims(_claims) do
    {:error, :reason_for_error}
  end

end
yordis commented 6 years ago

@tsurupin sorry I reopen this but this is a really nice documentation to have. Could you write about it please?

@ueberauth/core do you think is worth to add some section in the Guardian documentation so people could benefits of this?

If not, I will close it again

hassox commented 6 years ago

@tsurupin from what I can see, you just need to add some custom claims to the token... is that correct?

If that is correct you can implement this using the build_claims callback in your implementation module.

I believe this would be equivalent assuming you're using distillery.

  config :api, Api.Guardian,
    issuer: "${FIREBASE_SERVICE_ACCOUNT_EMAIL}",
    subject: "${FIREBASE_SERVICE_ACCOUNT_EMAIL}",
    verify_module: Api.Guardian.JWT,
    allowed_algos: ["RS256"],
    ttl: {1, :hours},
    secret_key: {Api.GuardianSecretKey, :get_key, []}
defmodule Api.Guardian do
use Guardian, otp_app: :api
  @aud "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"

  def subject_for_token(_, _) do
    config(:subject)
  end

  def resource_from_claims(%{"uid" => uid} = _claims) do
    case Repo.get_by(User, %{uid: uid}) do
      nil -> {:error, :user_not_found}
      user -> {:ok, user}
    end
  end

  def fetch_secret() do
    JOSE.JWK.from_pem_file(System.get_env("SECRET_PEM_FILE_PATH"))
  end

  def build_claims(claims, %{uid: uid}, opts) do
    claims 
    |> Map.put("aud", @aud)
    |> Map.put("uid", uid)
  end
end