How do you create a Joken Signer?

I can create a JWT (RS256) and validate it by putting my private key in config. However, if I try to create a signer manually, I am getting “key undefined” and it is failing.

Here is the working method first:

## Working method, in config.exs
config :joken, 
    default_signer: [
        signer_alg: "RS256",
        key_pem: """
        -----BEGIN PRIVATE KEY-----
        MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCYTlPp7IAM5Iwc
        CN8xz6JAg7mI/4idyXvqU3WNph02ppGZIExWc1JmMKEaPEcolpBB1SwiFmBaU/Mv
        gLmbnCle2gYRpxQS4fFcz6...

Then I can run:

token_map = %{} |> Joken.Config.add_claim("iss", fn -> "My issuer" end, &(&1 == "My issuer"))
token = Joken.generate_and_sign!(token_map)
Joken.verify_and_validate(%{}, token)

But if I try to do it instead with a custom signer:

signer = Joken.Signer.create("RS256", %{"pem" => My.Token.private_key})
token = Joken.generate_and_sign!(token_map, signer)

This fails. In this case, My.Token.private_key is a string same as in config returned by function like:

def private_key do
   """
        -----BEGIN PRIVATE KEY-----
        MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCYTlPp7IAM5Iwc
        CN8xz6JAg7mI/4idyXvqU3WNph02ppGZIExWc1JmMKEaPEcolpBB1SwiFmBaU/Mv
        gLmbnCle2gYRpxQS4fFcz6U0Y1csgbho5ii+/F6HF/iBf9hvaq1zqdQL4nwNC3jL
        hTZWgpJtw1CgDdvT+KPxSXw303CYjsicFCoRIhhgWyt8dzqer0f/LPjFnmUa5i2J
        shUm2Mj+0aczMWDkUQWnCjFnEmSa...
   """
end

The signer = line is returned with this result as having undefined keys, which I presume is a problem:

%Joken.Signer{
  jwk: #JOSE.JWK<keys: :undefined, fields: %{}, ...>,
  jws: %JOSE.JWS{
    alg: {:jose_jws_alg_rsa_pkcs1_v1_5, :RS256},
    b64: :undefined,
    fields: %{"typ" => "JWT"}
  },
  alg: "RS256"
}

If I try to use this broken signer, I get:

protocol Jason.Encoder not implemented for %{:struct => Joken.Signer, :alg => “RS256”, :jwk => #JOSE.JWK<keys: :undefined, fields: %{}, …>, :jws => %JOSE.JWS{alg: {:jose_jws_alg_rsa_pkcs1_v1_5, :RS256, b64: :undefined, fields: %{“typ” => “JWT”}}, “exp” => 1728638956, “iss” => “Issuer”} of type Joken.Signer (a struct), Jason.Encoder protocol must always be explicitly implemented.

What am I doing wrong? Any thoughts?

You’re passing the signer into the extra_claims function argument, see: generate_and_sign!/4

So it should be: Joken.generate_and_sign!(token_map, %{}, signer).

1 Like

Thank you! I was not being clear about what was going in and out. I was making another similar mistake as you say another place.

This works

    signer_priv = Joken.Signer.create("RS256", %{"pem" => My.Token.private_key}) #custom signer
    signer_pub = Joken.Signer.create("RS256", %{"pem" => My.Token.public_key}) #custom signer
    token_config_map = %{} |> Joken.Config.add_claim("iss", fn -> "My issuer" end, &(&1 == "My issuer")) 
    {:ok, token_claims} = Joken.generate_claims(token_config_map);
    {:ok, token, _} = Joken.encode_and_sign(token_claims, :default_signer)
    {:ok, token, _} = Joken.encode_and_sign(token_claims, signer_priv)
    Joken.verify_and_validate(%{}, token)
    Joken.verify_and_validate(%{}, token, signer_priv)
    Joken.verify_and_validate(%{}, token, signer_pub)