Verifying JWS E256 signature using the server’s public key Apple sign-in

https://developer.apple.com/documentation/signinwithapplerestapi/verifying_a_user


As per the above link, to verify an IDToken signature, Apple's public key needs to be used.


I am able to fetch Apple's public key, but I am unable to verify the signature of IDToken via the public key. I am trying to implement this in Java.. What exactly is meant by "JWS E256 signature"? Which algorithm?


Also, once public key is fetched (sample PK posted below), which algorithm to use to form the public key?

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "AIDOPK1",
      "use": "sig",
      "alg": "RS256",
      "n": "someValue",
      "e": "someValue"
    }
  ]
}

Accepted Reply

Basically, n is the RSA key modulus and e is exponent. They are both encoded with Base64 URL format. With them you create the RSA public key and use it with JWT library like JJWT to decode the JWT token. Here is a very basic example on Java:


import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.*;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;

private static PublicKey getPublicKey() throws Exception {
  String publicKeyString = N;
  String publicKeyExponent = E;

  BigInteger n = new BigInteger(1, Decoders.BASE64URL.decode(publicKeyString));
  BigInteger e = new BigInteger(1, Decoders.BASE64URL.decode(publicKeyExponent));

  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  KeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
  PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

  return publicKey;
}

private static void decodeJwt(String jwt) throws Exception {
  PublicKey publicKey = getPublicKey();

  Jwts.parser()
  .setSigningKey(publicKey)
  .parseClaimsJws(jwt)
  .getBody(); // will throw exception if token is expired, etc.
}

Replies

Basically, n is the RSA key modulus and e is exponent. They are both encoded with Base64 URL format. With them you create the RSA public key and use it with JWT library like JJWT to decode the JWT token. Here is a very basic example on Java:


import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.*;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;

private static PublicKey getPublicKey() throws Exception {
  String publicKeyString = N;
  String publicKeyExponent = E;

  BigInteger n = new BigInteger(1, Decoders.BASE64URL.decode(publicKeyString));
  BigInteger e = new BigInteger(1, Decoders.BASE64URL.decode(publicKeyExponent));

  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  KeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
  PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

  return publicKey;
}

private static void decodeJwt(String jwt) throws Exception {
  PublicKey publicKey = getPublicKey();

  Jwts.parser()
  .setSigningKey(publicKey)
  .parseClaimsJws(jwt)
  .getBody(); // will throw exception if token is expired, etc.
}

Hi @gramsta

I am trying to follow you, but I am getting Verify the JWS E256 signature using the server’s public key

My codes are as follows -


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

import org.junit.jupiter.api.Test;
import org.springframework.boot.web.client.RestTemplateBuilder;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.List;

public class AppleTest {

    @Test
    public void test(String appleToken) throws Exception {
        
        AppleKeySet appleKeySet = new RestTemplateBuilder().build()
                .getForObject("https://appleid.apple.com/auth/keys", AppleKeySet.class);

        List<Key> applePublicKeys = appleKeySet.getKeys();
        Key key = applePublicKeys.get(0);

        BigInteger n = new BigInteger(1, Base64.getUrlDecoder().decode(key.getN()));
        BigInteger e = new BigInteger(1, Base64.getUrlDecoder().decode(key.getE()));

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        KeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

        Claims claims = Jwts.parser()
                .setSigningKey(publicKey)
                .parseClaimsJws(appleToken)
                .getBody();
    }
}

Key POJO:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Key {

    @JsonProperty("kty")
    private String kty;
    @JsonProperty("kid")
    private String kid;
    @JsonProperty("use")
    private String use;
    @JsonProperty("alg")
    private String alg;
    @JsonProperty("n")
    private String n;
    @JsonProperty("e")
    private String e;
}

I am using implementation io.jsonwebtoken:jjwt:0.9.1 for JWT. What could be wrong?

appleToken == JWT received during Sign In With Apple = Identity Token

Hi

how should we do the same in C#?