Skip to content

Instantly share code, notes, and snippets.

@lud
Created July 21, 2025 08:09
Show Gist options
  • Select an option

  • Save lud/2b0c681ef6ab45bea77459cc7dc0ece8 to your computer and use it in GitHub Desktop.

Select an option

Save lud/2b0c681ef6ab45bea77459cc7dc0ece8 to your computer and use it in GitHub Desktop.
How to decode a Phoenix.Token manually
data = "hello"
salt = "foo"
token = Phoenix.Token.sign(PotsWeb.Endpoint, salt, data)
IO.inspect(token, limit: :infinity, label: "token")
# How to manipulate token to get back "hello" from it without using
# Phoenix.Token functions
decode_token = fn token ->
[protected, payload, signature] = String.split(token, ".")
# Decode the payload and signature
{:ok, decoded_payload} = Base.url_decode64(payload, padding: false)
{:ok, decoded_signature} = Base.url_decode64(signature, padding: false)
{decoded_data, _, _} = :erlang.binary_to_term(decoded_payload)
# Recreate the HMAC signature using the same method as Plug.Crypto
key_base = PotsWeb.Endpoint.config(:secret_key_base)
digest_type =
case protected do
"SFMyNTY" -> :sha256
"SFMzODQ" -> :sha384
"SFM1MTI" -> :sha512
end
# Use Plug.Crypto.KeyGenerator to derive the actual signing key
# Default options from Phoenix.Token: key_iterations: 1000, key_length: 32, key_digest: :sha256
signing_key =
Plug.Crypto.KeyGenerator.generate(key_base, salt,
key_iterations: 1000,
key_length: 32,
key_digest: digest_type
)
plain_text = [protected, ?., payload]
recreated_signature = :crypto.mac(:hmac, digest_type, signing_key, plain_text)
# Verify the signature
if Plug.Crypto.secure_compare(recreated_signature, decoded_signature) do
decoded_data
else
raise "Invalid token signature"
end
end
extracted_data = decode_token.(token)
if extracted_data == data do
IO.puts("ok!")
else
raise "Expected data to be #{inspect(data)} but got: #{inspect(extracted_data)}"
end
# Now if you just want the data without verifying the validity of the token, you
# only need this:
get_data = fn token ->
[_, payload | _] = String.split(token, ".")
{:ok, decoded_payload} = Base.url_decode64(payload, padding: false)
{decoded_data, _, _} = :erlang.binary_to_term(decoded_payload)
decoded_data
end
extracted_data = get_data.(token)
if extracted_data == data do
IO.puts("ok!")
else
raise "Expected data to be #{inspect(data)} but got: #{inspect(extracted_data)}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment