MapKit JS in .NET Core

January, 2020

While searching the web, I was unable to find an example of signing a JWT (Javascript Web Token) in .NET Core for embedding an Apple Map with MapKit JS in a .NET Core web site. Apple requires that a JWT Token be passed to their API that is signed with a private key. Although the JWT could be hard coded in the page, it is preferred that a MapKit JS callback calls a REST service on the server to get the JWT.

Apple supplies the private key for in a PKCS#8 file. See here for some instructions on generating and downloading the private key. Note that although there are other ways to import the private key, I have chosen to convert the key into a simple base 64 encoded string. This allows the private key to be easily stored in an environment variable that's accessible to the app or in a configuration file.

The first step is creating a SecurityKey object that can be used to sign the JWT Token with the private key. Apple requires that the ES256 algorithm be used to sign the JWT. The key can be encoded as an ECDsaSecurityKey. The following method takes the input key, converts the key to a byte array and stores the data into an ECDsa object. It uses the ECDsa object to create the ECDsaSecurityKey object.

Note that the ImportPkcs8PrivateKey method was added in .NET Core 3.

private static SecurityKey GetES256IssuerSigningKey(string es256PrivateKey)
{
    var ecDsa = ECDsa.Create();
    ecDsa.ImportPkcs8PrivateKey(Convert.FromBase64String(es256PrivateKey), out _);
    return new ECDsaSecurityKey(ecDsa);
}

The next step is generating the JWT token. Three pieces of information are supplied here: the private key string, the key id supplied by Apple, and the Org Id associated with the Apple developer account where the key was created.

private string GenerateToken()
{
    var now = DateTime.UtcNow;

    var header = new JwtHeader(
                new SigningCredentials(
                    GetES256IssuerSigningKey("<Concat the private key into a string and pass here>"),
                    "ES256"));
    header.Add("kid", "<key id>");

    var payload = new JwtPayload("<org id>",
                                 string.Empty, // Website URL is encoded here
                                 new Claim[0],
                                 now,
                                 now.AddDays(10),
                                 now );

    // Create the JWT and write it to a string
    var jwt = new JwtSecurityToken(
                header,
                payload);
            
    return new JwtSecurityTokenHandler().WriteToken(jwt);
}

Note that I do not supply an origin restriction into the JWT token, this still should be done in production to restrict the web sites that can use the token. Without the origin restriction being set, a warning message is displayed in the console window of the browser by MapKit JS.

From this point, the security token can be returned by a simple MVC controller. The MapKit JS API is passed a delegate that can be used by the API to retrieve the token. The following is a simple controller I used to return the token.

[Route("api/maps")]
[ApiController]
public class MapsApiController : ControllerBase
{
    // GET: api/maps
    [HttpGet(Name = "Get")]
    public string Get()
    {
        return GenerateToken();
    }
}

The following code can be used to initialize MapKit JS in a page.

<script>
    mapkit.init({
        authorizationCallback: function (done) {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", "/api/maps");
            xhr.addEventListener("load", function () {
                done(this.responseText);
            });
            xhr.send();
        }
    });
</script>

That's it! There are other sites with more details on how to add landmarks and utilize other aspects of the MapKit JS API.