WeatherKit REST API Authentication

Hi there! Please could you publish some of the specifics for authentication for the new WeatherKit REST API? I am attempting to use something cobbled together from MapKit, and unfortunately WeatherKit returns a 500 error in response.

Broken code sample below. If you find a working solution or can identify an error, please comment with the solution. Thank you!

require 'http'
require 'jwt'
require 'openssl'
require 'logger'

p8 = OpenSSL::PKey::EC.new(File.read("/PATH/TO/DOWNLOADED/P8/FILE"))

token = JWT.encode({
  iss: 'KEY_NAME_FROM_CIP',
  iat: Time.now.to_i,
  exp: Time.now.to_i + 3600,
  aud: 'weatherkit',
}, p8, 'ES256', {
  kid: 'KEY_ID_FROM_CIP',
  typ: 'JWT'
})

res = HTTP.use(logging: {
  logger: Logger.new(STDOUT)
}).headers(
  "Authorization" => "Bearer #{token}"
).get "https://weatherkit.apple.com/api/v1/weather/en/LONG/LAT?dataSets=currentWeather&timezone=Europe/London"

puts "code: #{res.code}"
puts res.body
Post not yet marked as solved Up vote post of rubynerd Down vote post of rubynerd
7.6k views
  • Is there a public key to utilize at jwt.io in order to sign my token with my WeatherKit private key (so that I can then test it in Postman agnostic of any other code - since one cannot avoid 'Invalid Signature' after otherwise using their private key on jwt.io in attempt to sign it)?

Add a Comment

Replies

I'm the same here. Apparently, the APIs are still not available.

I was really hoping the session would be more informative, but they simply said "[you need to] create the header containing the fields and values described in the developer documentation", when the developer documentation literally has no mention that this API uses JWTs.

@leemartin reached out to Novall on Twitter to ask when the REST API will be functional, but hasn't received a response yet. Twitter isn't allowed to be linked, but the text of the link is permitted: https://twitter.com/leemartin/status/1534214645128564737

Really excited to get cracking with this, but looks like it's half-baked at the moment.

  • @rubynerd - is there any possible chance you might be able to document how to set this up in Postman (so it is generally agnostic of other code)? I am getting a 403 and have yet to determine the culprit. Thanks for any possible consideration, and for everything you already did here.

Add a Comment

This video contains some details about WeatherKit Auth starting at 9:05 - https://developer.apple.com/videos/play/wwdc2022/10003/

The example code in the video omitted the "Bearer " prefix from the Authorization header

  • The Bearer prefix is not relevant. I tried with and without „bearer“. Whenever a JWT token is used you will get 500 Internal Server Error - content of the JWT doesn’t play a role. As soon as you omit the authentication header or write random stuff into the field you will get 401 Unauthorized. I think the JWT validation has a bug and crashes on each validation (explains 500). A correct working validation would return 401 Unauthorized when JWT isn’t valid.

  • The Bearer prefix is optional. The prefix helps identifying what type of token authentication you are delivering. If there is only bearer token to accept, most APIs simply remove "Bearer " from Authorization header string. WeatherKit REST API seems to do this, too.

Add a Comment

Seems like there was an update this night. Now I'm getting 401 instead of 500.

Anyone get this working yet?

I'm getting a 500 with a node app

Looks like there is a url issue? If I use the urls in the docs:

https://weatherkit.apple.com/api/v1/weather/en

then I get a 500.

If I use the url as in the video

https://weatherkit.apple.com/1/weather/

then I get a 401

I've opened a feedback request (10140066) with the following:

WeatherKit REST API documentation is missing authentication details

The API documentation for WeatherKit's REST API is missing the authentication details that were mentioned during the session video. Several other engineers have also reported this via the forums, without any official response. To reproduce, visit https://developer.apple.com/documentation/weatherkitrestapi/get_api_v1_weather_language_latitude_longitude and observe the lack of details regarding authentication. I expect to be able to understand how to authenticate with this API from its documentation, but the documentation is missing, however, any reference to authentication is missing. I am not using a version of Xcode to encounter this issue.

Would really appreciate a timeline from Apple for a resolution!

Additionally, @markdaws on Twitter has also followed up to another WeatherKit tweet expressing issue with authentication for this API. Tweet links as code are now banned, so here's a gigantic picture instead:

SUCCESS! Had a labs call with two Apple engineers and they set me on the right path.

This is what your JWT should look like:

var privateKey = fs.readFileSync("YOU KEY FILE FROM DEVELOPER CENTER.p8");
  var token = jwt.sign(
    {
      subject: "APP ID",
    },
    privateKey,
    {
      jwtid: "YOUR TEAM ID.YOUR APP ID",
      issuer: "TEAM ID",
      expiresIn: "1h",
      keyid: "KEY ID",
      algorithm: "ES256",
      header: {
        id: "YOUR TEAM ID.YOUR APP ID",
      },
    }
  );
  // id is teamid.serviceid

  const url =
    "https://weatherkit.apple.com/api/v1/weather/en/51.677612/-2.937941?dataSets=currentWeather&timezone=Europe/London";

  const config = {
    headers: { Authorization: `Bearer ${token}` },
  };

  const { data: weatherData } = await axios.get(url, config);

Getting the id on the header is the trick.

I have made a YouTube video and example app but it won't let me post the link, the youtube video id is: 7mg42_Fix9k

@tanakasan1734 you legend, thank you for staying up till 2am working on this!!

Thank you so much for putting this up as a YouTube video as well, really appreciate it! I've converted your JavaScript example into my native Ruby & included it below.

For those still having issues with this API: of significant importance is setting up an "app" in Certificates, Identifiers & Profiles in addition to setting up the "key", so you have an "app ID" registered for WeatherKit in addition to the key being assigned access to it. I would imagine this has something to do with the upcoming billing for WeatherKit.

Full annotated example from my lab notebook:

# WeatherKit REST API: access example
# With thanks to Simon of All The Code (Twitter: @allthecode_)

require 'openssl' # stdlib
require 'logger' # stdlib
require 'http' # gem install http
require 'jwt' # gem install jwt

## APPLE ID's (mine are examples)
TEAM_ID = "TVVX2ENCS3" # Your Apple Developer "team ID", see https://developer.apple.com/account/#!/membership -> "Team ID"
APP_ID = "net.rubynerd.weathervane" # Create an Identifier -> App ID -> App -> set Bundle ID -> App Services -> check WeatherKit -> Save, use Bundle ID here
KEY_ID = "HC2L4UY9UW" # Create a Key -> set Key Name -> check WeatherKit, use Key ID here
KEY_PATH = "/Users/rubynerd/Downloads/AuthKey_HC2L4UY9UW.p8" # Path to file downloaded from key creation above

## GEO VARS
# Charing Cross, nominal centre of London
LAT = "51.5081"
LONG = "0.1248"
TZ = "Europe/London"
DATASETS = %w{currentWeather}.join(",")

# Read the key path to create an ECDSA key
p8 = OpenSSL::PKey::EC.new(File.read(KEY_PATH))

# Create a JWT for accessing WeatherKit
jwt = JWT.encode({
  iss: TEAM_ID,
  iat: Time.now.to_i,
  exp: Time.now.to_i + 3600,
  sub: APP_ID,
  jti: "#{TEAM_ID}.#{APP_ID}",
}, p8, 'ES256', {
  kid: KEY_ID,
  id: "#{TEAM_ID}.#{APP_ID}",
})

# Use httprb (https://github.com/httprb/http) to make the request to WeatherKit
res = HTTP.use(logging: {
  logger: Logger.new(STDOUT)
}).headers(
  "Authorization" => "Bearer #{jwt}",
).get "https://weatherkit.apple.com/api/v1/weather/en/#{LAT}/#{LONG}?dataSets=#{DATASETS}&timezone=#{TZ}"

# Parse the response body from JSON to a Ruby Hash
body = res.parse

# Print the condition code
puts "conditionCode: #{body["currentWeather"]["conditionCode"]}"
# Print required attribution & link
puts "data provided by Apple WeatherKit (#{body["currentWeather"]["metadata"]["attributionURL"]})"

If you're translating the above from Ruby to another language, I've included an expired JWT you can paste into jwt.io's debugger:

eyJraWQiOiJIQzJMNFVZOVVXIiwiaWQiOiJUVlZYMkVOQ1MzLm5ldC5ydWJ5bmVyZC53ZWF0aGVydmFuZSIsImFsZyI6IkVTMjU2In0.eyJpc3MiOiJUVlZYMkVOQ1MzIiwiaWF0IjoxNjU0ODU0NTU5LCJleHAiOjE2NTQ4NTQ1NjQsInN1YiI6Im5ldC5ydWJ5bmVyZC53ZWF0aGVydmFuZSIsImp0aSI6IlRWVlgyRU5DUzMubmV0LnJ1YnluZXJkLndlYXRoZXJ2YW5lIn0.7mGJZvce6D2JmbligmurJc0H4sMa_CwCwHBB4a5yoQvh9n7AljVOpDp7vHblWRG-DPtSqSFzOflM92otKkgQSw

Between the JS and Ruby, this should be enough to close this. I'll leave the feedback request open to push for accurate documentation. Let me know if you have any issues with what's above, or DM me on Twitter if you need further assistance.

  • Amazing work as well @rubynerd glad it was able to get you moving and productive again

  • Could we generate the token using JWT.io? I tried to do that but JWT.io requires another public key to be able to sign my token.

  • This is working great for me -- thank you so much!

Add a Comment

Apple have published docs on this topic now and also modified the API so that now your JWT subject has to be sub and not subject as I was able to get away with in my first code example.

Yup the feedback request I opened was closed this morning: https://developer.apple.com/documentation/weatherkitrestapi/request_authentication_for_weatherkit_rest_api

I ended up integrating this on a new project already. 😅 https://www.3000degreez.app (Dev blog coming soon)

This setup had been working well for me until yesterday, and now I'm unable to authenticate. Has anyone else experienced this issue as well?

  • Same here. That code had been working until I returned to it today.

  • I was making a dumb mistake. I had Weatherkit enabled under "Capacites" - which seemed to be fine until a few weeks ago. Had to add it to "App Services" as indicated earlier in this thread.

Add a Comment

Same here... it worked in the betas, but stopped working now that I want to publish :( Weatherkit is enabled in both Capacities, and in the App Services...

I have decoded the JWT token and ensured it contains the exact same items as the sample token at https://developer.apple.com/documentation/weatherkitrestapi/request_authentication_for_weatherkit_rest_api

{
    "alg": "ES256",
    "kid": "3J4F34**24",
    "id": "KC697SNQ2X.com.caramba.instaweather"
}
{
    "iss": "KC697SNQ2X",
    "iat": 1437179036,
    "exp": 1493298100,
    "sub": "com.caramba.instaweather"
}

response is

{"timestamp":"2022-09-16T18:17:15Z","status":403,"error":"Forbidden","message":"Access Denied","path":"/api/v1/availability/37.323/122.032"}   
Add a Comment