pedrocaseiro
How to implement Apple Login in a Phoenix app?
Hey everyone!
I’m currently implementing sign in with apple on my app. I’m already at the point where I got the JWT that I need to decode to validate the user and the key hash from apple (includes the kid, the alg etc…
How can I use the information from the key hash to decode the JWT correctly?
In Ruby it seems pretty straight forward with
keyHash = ActiveSupport::HashWithIndifferentAccess.new(apple_certificate["keys"].select {|key| key["kid"] == kid}[0])
jwk = JWT::JWK.import(keyHash)
token_data = JWT.decode(jwt, jwk.public_key, true, {algorithm: alg})[0]
But I’m having a hard time finding the equivalent in Elixir. I took a look at JOSE.JWK but I’m not sure which method I should use to generate the JWK and fetch the public key afterward
Thanks!
Most Liked
Udi
defmodule Accounts.AppleAuth do
@apple_keys_url "https://appleid.apple.com/auth/keys"
@aud "com.blahblah"
def verify_identity_token(identity_token) do
# Fetch Apple's public keys
case get_apple_public_keys() do
{:ok, apple_keys} ->
with {:ok, token} <- decode_and_verify(identity_token, apple_keys) do
verify_claims(token)
end
{:error, reason} ->
{:error, reason}
end
end
defp get_apple_public_keys() do
case HTTPoison.get(@apple_keys_url) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
case Jason.decode(body) do
{:ok, %{"keys" => keys}} ->
{:ok, keys}
{:error, _} ->
{:error, "Failed to decode Apple's public keys"}
end
{:ok, %HTTPoison.Response{status_code: status_code}} ->
{:error, "Failed to fetch Apple's public keys, status code: #{status_code}"}
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, "HTTP request failed: #{reason}"}
end
end
defp decode_and_verify(token, apple_keys) do
# Find the appropriate key for the token
key = find_apple_key(token, apple_keys)
if key do
jwk = JOSE.JWK.from(key)
{verified, jwt, _} = JOSE.JWT.verify_strict(jwk, ["RS256"], token)
if verified, do: {:ok, jwt}, else: {:error, "Invalid token signature"}
else
{:error, "No matching key found"}
end
end
defp verify_claims(jwt) do
%JOSE.JWT{fields: claims} = jwt
case claims do
%{"aud" => @aud, "exp" => exp} ->
current_time = System.system_time(:second)
if exp > current_time do
{:ok, claims}
else
{:error, "Token has expired"}
end
_ ->
{:error, "Invalid claims"}
end
end
defp find_apple_key(token, keys) do
# Decode token header to find matching `kid`
%JOSE.JWS{fields: %{"kid" => kid}} = JOSE.JWT.peek_protected(token)
Enum.find(keys, fn key -> key["kid"] == kid end)
end
end
The above seems to be working for me! Hope it helps someone out one day.
jayden
I recently implemented Sign in with Apple in our app, and I used Joken to handle both token verification and client secret generation.
Here’s what I did to decode the JWT (Note: My example uses Joken v1.5)
token = Joken.token(id_token)
%{"alg" => alg, "kid" => key_id} = Joken.peek_header(token)
token
|> Joken.with_signer(Signer.rs(alg, public_key))
|> Joken.with_validation("aud", &(&1 == "<client_id>"))
|> Joken.with_validation("exp", &(&1 >= System.os_time(:second)))
|> Joken.with_validation("iss", &(&1 == "<issuer>"))
|> Joken.verify!()
public_key above is the matching key fetched from Apple’s public key endpoint.
Hope this helps - and feel free to reach out with additional questions
JeyHey
With Assent it is very easy too (in my mix file: {:assent, "~> 0.1.13"}). You just have to pass the apple authorization code (ASAuthorizationAppleIDCredential → authorizationCode) and the apple_id is then verified. No need to manually pass the public key and algorithm. You don’t even need to pass the identityToken:
def check_apple_sign_in(_apple_jwt, apple_auth) do
[
client_id: "com.your_domain.your_app",
team_id: "YOUR_ID",
private_key_id: "KEY_ID",
private_key_path: "/Users/you/development/Keys/AuthKey_2YZ4LV4PYI.p8",
redirect_uri: nil
]
|> Assent.Config.put(:session_params, %{})
|> Assent.Strategy.Apple.callback(%{"code" => apple_auth})
|> case do
{:ok, %{user: user, token: token}} ->
%{"sub" => user_id} = user
Logger.info "apple sign in verification successful. user: #{Kernel.inspect(user)}; token: #{Kernel.inspect(token)}"
{:ok, user_id} #the user_id you are interested in
error ->
Logger.info "apple sign in verification not successful: #{Kernel.inspect(error)}"
{:error, :authentication}
end
end
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








