Closed tonnenpinguin closed 3 years ago
Yeah, the docs should describe this better. In the docs it's described how the state is generated with Assent.Strategy.Apple.authorize_url/1
:
{:ok, %{session_params: %{state: state}} = Assent.Strategy.Apple.authorize_url(config)
You would want to store the :session_params
in the user session, as that is what'll be used in the callback:
{:ok, %{user: user, token: token}} =
config
|> Assent.Config.put(:session_params, session_params)
|> Assent.Strategy.Apple.callback(params)
In the Assent docs it doesn't show any example like this as it depends how you deal with the user session in your app. The above is essentially what PowAssent does for you. So if you are using PowAssent, you can just leverage it.
You can do something like the below in your controller to render the sign in with apple button (from the top of my head):
def new(conn, _params) do
redirect_uri = Routes.pow_assent_authorization_url(conn, :callback, "apple")
case PowAssent.Plug.authorize_url(conn, "apple", redirect_uri) do
{:ok, _url, conn} ->
conn
|> PowAssent.Plug.put_session(conn, :session_params, conn.private[:pow_assent_session_params])
|> render("new.html",
state: conn.private[:pow_assent_session_params][:state],
scope: "email",
redirect_uri: redirect_uri,
client_id: Application.get_env(:pow_assent, :my_app)[:providers][:apple][:client_id]
))
{:error, error, conn} ->
# handle error
end
end
And render the button with:
<div id="appleid-signin" data-type="sign in"></div>
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
<script type="text/javascript">
AppleID.auth.init({
clientId: '<%= @client_id %>',
scope: '<%= @scope %>',
redirectURI: '<%= @redirect_uri %>',
state: '<%= @state %>'
})
</script>
I'm not sure how this can be detailed better in the Assent docs though as it's very specific to your app (though most would be using Phoenix/Plug). I'm thinking that maybe it would be helpful to have strategy specific helpers in PowAssent, like rendering the Sign in with Apple button.
Thanks for the elaborate answer!
Basically what I think would have been a good starting point is an explicitly pointer to pow_assent alongside the other usage infos.
When I first looked at the docs it seemed to me as if it was expected from me to implement the apple sign in with the JS SDK, rather than a pure phoenix flow.
That brings me to another thing I've been struggling with: Getting the users name.
Apple apparently decided not to include the user name in the identity token and also does not expose a user info endpoint. The only way I could come up with is to implement my own version of the Apple Strategy and then polyfill the user object that has been created based on the identity token with the parameters passed by apple as post attributes.
defmodule Auth.Strategy.Apple do
use Assent.Strategy.OIDC.Base
alias Assent.Strategy.Apple, as: AppleAssent
@impl true
def default_config(config) do
config
|> AppleAssent.default_config()
|> Keyword.put(:authorization_params, scope: "email name", response_mode: "form_post")
end
def callback(config, params) do
config
|> AppleAssent.callback(params)
|> polyfill_user_from_params(params)
end
defp polyfill_user_from_params({:ok, %{user: user} = results}, %{"user" => user_params}) do
updated_user = update_user_name_from_params(user, user_params)
{:ok, %{results | user: updated_user}}
end
defp polyfill_user_from_params(callback_result, _params), do: callback_result
defp update_user_name_from_params(user, user_params_string)
when is_binary(user_params_string) do
with {:ok, user_params} <- Jason.decode(user_params_string) do
update_user_name_from_params(user, user_params)
end
end
defp update_user_name_from_params(user, %{
"name" => %{"firstName" => first_name, "lastName" => last_name}
}) do
Map.put(user, "name", "#{first_name} #{last_name}")
end
defp update_user_name_from_params(user, _user_params), do: user
defdelegate normalize(config, user), to: Assent.Strategy.Apple
end
Any chance you've come up with a more elegant solution to this?
Sorry, completely missed your reply! Your polyfill could be done with just adding a get_user/2
method:
@impl true
def get_user(config, token) do
case OIDC.get_user(config, token) do
{:ok, user} -> {:ok, update_user_name_from_params(user, token)}
{:error, error} -> {:error, error}
end
end
But really this is something the strategy should handle instead. All the strategies in assent should conform to standard claims format, so you only have to write logic for e.g. combining first and last name once and can use it for any strategies (like in the changeset). I've opened #55 to resolve this with the Apple strategy, let me know if it works for you @tonnenpinguin!
It has been merged to master so you can test it out by using {:assent, github: "pow-auth/assent", override: true}
.
Hi guys,
While implementing the endboss, aka sign in with Apple I ran into some issues.
The docs in
Assent.Strategy.Apple
specifically talk about the JS SDK, but I couldn't figure out how to get the state to be passed in and read back correctly.In the end I resorted to just using the
Routes.pow_assent_authorization_url/3
, which worked like a charm.Long story short, it would be great to give people a pointer to just using the regular
/auth/apple/new
flow, especially since the apple docs only talk about their JS SDK as well.