Last active
April 9, 2024 09:59
-
-
Save gor181/cf65285e2225bdf629862460835aed40 to your computer and use it in GitHub Desktop.
Apple - API - Authentication
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Project.AppleApi do | |
@moduledoc """ | |
Apple API: https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api | |
Relies on Confex and having the following configuration in config/config.exs | |
``` | |
config :project, Project.Apple, | |
api_url: {:system, "PROJECT_APPLE_URL", ""}, | |
certificate: {:system, "PROJECT_APPLE_CERTIFICATE", ""}, | |
iss: {:system, "PROJECT_APPLE_ISS", ""}, | |
bid: {:system, "PROJECT_APPLE_BID", ""}, | |
kid: {:system, "PROJECT_APPLE_KID", ""} | |
``` | |
Additionally, make sure to inject the environment variables (e.g. via envrc.local using direnv): | |
``` | |
export PROJECT_APPLE_URL=https://api.storekit-sandbox.itunes.apple.com | |
export PROJECT_APPLE_CERTIFICATE=BASE_64_ENCODED_PRIVATE_KEY_FROM_APPLE | |
export PROJECT_APPLE_ISS=ISS | |
export PROJECT_APPLE_BID=app.yourapp.YourApp.State | |
export PROJECT_APPLE_KID=2X9R4HXF34 | |
``` | |
Dependencies required: | |
``` | |
{:joken, "~> 2.5"}, | |
{:jose, "~> 1.11"}, | |
``` | |
""" | |
require Finch | |
require Logger | |
@doc """ | |
Retrieves the transaction | |
""" | |
def get_transaction(transaction_id) do | |
api_url = "#{get_config(:api_url)}/inApps/v1/transactions/#{transaction_id}" | |
receive_timeout = 4_000 | |
case "GET" | |
|> build(api_url, get_headers(), %{}) | |
|> Finch.request(FinchHttpClient, receive_timeout: receive_timeout) do | |
{:ok, %{status: 200, body: body}} -> | |
body = Jason.decode!(body, keys: :atoms) | |
{:ok, decode_data(body.signedTransactionInfo)} | |
{:ok, %{status: 404}} -> | |
{:error, :not_found} | |
{:error, %Mint.TransportError{reason: :timeout}} -> | |
{:error, :timeout} | |
{ok_nok, response} -> | |
Logger.error( | |
"#{__MODULE__}: handle_send_response -> ok_nok:#{ok_nok} -> #{inspect(response)}" | |
) | |
{:error, :general} | |
end | |
end | |
defp build(method, uri, headers, nil) do | |
Finch.build(method, uri, headers_to_list(headers)) | |
end | |
defp build(method, uri, headers, body) when is_binary(body) do | |
Finch.build(method, uri, headers_to_list(headers), body) | |
end | |
defp build(method, uri, headers, body) when is_map(body) and map_size(body) == 0 do | |
Finch.build(method, uri, headers_to_list(headers)) | |
end | |
defp build(method, uri, headers, body) when is_map(body) do | |
Finch.build(method, uri, headers_to_list(headers), Jason.encode!(body)) | |
end | |
defp headers_to_list(headers) do | |
headers | |
|> Map.to_list() | |
|> Enum.map(fn {key, value} -> {to_string(key), value} end) | |
end | |
defp get_config(key) do | |
Confex.fetch_env!(:capi, __MODULE__) |> Keyword.get(key) | |
end | |
defp get_headers() do | |
%{"Content-Type": "application/json", Authorization: "Bearer #{get_token()}"} | |
end | |
defp decode_data(data) do | |
JOSE.JWT.peek_payload(data) |> Map.get(:fields) | |
end | |
defp get_token do | |
certificate = get_config(:certificate) | |
iss = get_config(:iss) | |
bid = get_config(:bid) | |
kid = get_config(:kid) | |
{_, key_map} = | |
certificate | |
|> Base.decode64!() | |
|> JOSE.JWK.from_pem() | |
|> JOSE.JWK.to_map() | |
signer = Joken.Signer.create("ES256", key_map, %{ | |
alg: "ES256", | |
kid: kid, | |
typ: "JWT" | |
}) | |
claims = %{ | |
iss: iss, | |
iat: :os.system_time(:second), | |
exp: :os.system_time(:second) + 3599, | |
aud: "appstoreconnect-v1", | |
bid: bid | |
} | |
{:ok, secret} = Joken.Signer.sign(claims, signer) | |
secret | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment