Apple Maps Server API Unauthorized Error

I'm trying to use the new Apple Maps Server API. As far as I can tell I have set up the token correctly, but my web request is still returning 401 unauthorized.

The following is my code in TypeScript:

import * as jwt from 'jsonwebtoken';

const JWT_SECRET = "-----BEGIN PRIVATE KEY-----\n" +
"MIGTAgEAMBMGBy..............................\n" +
"..............................................................\n" +
"..............................................................\n" +
"-----END PRIVATE KEY-----";

const header = {
alg: "ES256",
kid: "26DYPK65ZK",
typ: "jwt"
}

// Example payload data
const payload = {
"iss": "7F3PBYWYMS",
"iat": Date.now(),
"exp": Date.now() + (1000 * 30 * 60),
};

export async function getRestaurants() {

let token = jwt.sign(payload, JWT_SECRET, { algorithm: 'ES256', header: header});
const response = await fetch('https://maps-api.apple.com/v1/token (https://maps-api.apple.com/v1/token)', {
method: 'GET',
headers: {
'Authorization': "Bearer " + token
},
});
console.log(response);

}

What am I doing incorrectly here?

Answered by DTS Engineer in 811138022

Here's a working implementation for generating an Apple Maps Server token using Node:

const fs = require('fs')
const jwt = require('jsonwebtoken')

// Configure these values
const teamID = "AB12CD34E5"
const mapsServerKeyID = "A1BCDEFGHI"
let authKey = fs.readFileSync("./auth/Maps_Server_AuthKey.p8"); // P8 file from Apple Developer Portal

let payload = {
	iss: teamID,
	iat: Date.now() / 1000,
	exp: (Date.now() / 1000) + (60 * 60 * 24 * 7), // 7 days
};

let header = {
	kid: mapsServerKeyID,
	typ: "JWT",
	alg: "ES256"
};

let token = jwt.sign(payload, authKey, { header: header })
console.log(token)

One thing to double check on is that you didn't accidentally flip your team ID and the Maps ID (kid) values, since they look similar. Also, your expiration and date fields may not be calculated correctly, they need to be specified in seconds, which my code above is doing because Date.now() returns in milliseconds.

—Ed Ford,  DTS Engineer

What error are you seeing?

Response {   status: 401,   statusText: 'Unauthorized',   headers: Headers {     server: 'Apple',     date: 'Fri, 04 Oct 2024 21:25:27 GMT',     'content-type': 'application/json;charset=utf8',     'content-length': '71',     connection: 'keep-alive',     'cache-control': 'max-age=0',     'x-rid': 'f2e9c40e-67a5-4b2c-b8af-586af6a27f72',     'content-encoding': 'gzip',     'x-envoy-upstream-service-time': '26',     'strict-transport-security': 'max-age=31536000; includeSubdomains',     'x-content-type-options': 'nosniff',     'x-xss-protection': '1; mode=block'   },   body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },   bodyUsed: false,   ok: false,   redirected: false,   type: 'basic',   url: 'https://maps-api.apple.com/v1/token' }

Here's a working implementation for generating an Apple Maps Server token using Node:

const fs = require('fs')
const jwt = require('jsonwebtoken')

// Configure these values
const teamID = "AB12CD34E5"
const mapsServerKeyID = "A1BCDEFGHI"
let authKey = fs.readFileSync("./auth/Maps_Server_AuthKey.p8"); // P8 file from Apple Developer Portal

let payload = {
	iss: teamID,
	iat: Date.now() / 1000,
	exp: (Date.now() / 1000) + (60 * 60 * 24 * 7), // 7 days
};

let header = {
	kid: mapsServerKeyID,
	typ: "JWT",
	alg: "ES256"
};

let token = jwt.sign(payload, authKey, { header: header })
console.log(token)

One thing to double check on is that you didn't accidentally flip your team ID and the Maps ID (kid) values, since they look similar. Also, your expiration and date fields may not be calculated correctly, they need to be specified in seconds, which my code above is doing because Date.now() returns in milliseconds.

—Ed Ford,  DTS Engineer

Hi Ed, I'm not sure where difference is between, the code I included in my question and the sample code you provided as a response. Here is an updated version of my code which still doesn't work but is designed as close as possible to the code you provided as possible.

const jwt = require('jsonwebtoken')
const fs = require('fs')

const authKey = fs.readFileSync("../../Desktop/AuthKey_26DYPK65ZK.p8")
const teamID = "7F3PBYWYMS"
const mapsServerKeyID = "26DYPK65ZK"


const header = {
    alg: "ES256",
    typ: "JWT",
    kid: mapsServerKeyID,
}

// Example payload data
const payload = {
    "iss":  teamID,
    "iat": Date.now(),
    "exp": Date.now() + (60 * 60 * 24 * 7), // 7 days,

};

let token = jwt.sign(payload, authKey, {header: header}); 

export async function getRestaurants() {
    const response = await fetch('https://maps-api.apple.com/v1/token', {
        method: 'GET',
        headers: {
            'Authorization': "Bearer: " + token
        },
    });
    console.log(response)
}

can you please take a look and help me understand what I am doing incorrectly?

Notably I have tried: "Bearer " + token, "Bearer: " + token and just token in the header.

One observation here is to pay attention to the Date units. In my implementation, I'm dividing by 1000, as the Date.now value is in milliseconds for my server environment, and I'm converting units back up to seconds.

Beyond that, there's some very small syntactic differences to work through. It may seem like minutia, but I've had folks (for MapKit JS) identify small mistakes by having them go through the small items like these:

  1. Your header items are in a different order than mine.
  2. Your payload dictionary is quoting the key names, while the header dictionary is not. My implementation uses no quotes.
  3. You are constructing the bearer request through string building. If you eliminate that, and just send the token manually to the App Maps Servers with a curl command in Terminal (see the example on this page), that's one more chunk of code you can rule in or out of your implementation.

— Ed Ford,  DTS Engineer

Apple Maps Server API Unauthorized Error
 
 
Q