1<!DOCTYPE html> 2<html> 3 4 <head> 5 <meta charset="utf-8"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <meta name="viewport" content="width=device-width, initial-scale=1"> 8 9 <title>DarkSky to WeatherKit: from API keys to signed JWTs</title> 10 <meta name="description" 11 content=""> 12 <meta http-equiv="Content-Security-Policy" 13 content="default-src 'self' http://localhost:4000/ http://platform.twitter.com/ https://buttons.github.io/ https://github.githubassets.com/ https://gist.github.com/ https://az416426.vo.msecnd.net/next/ https://dc.services.visualstudio.com/v2/track 'unsafe-inline'; font-src 'self' data:; img-src 'self' https://img.youtube.com https://dev-to-uploads.s3.amazonaws.com/"> 14 15 16 <!-- <link rel="stylesheet" href="https://jpda.dev/assets/style.css"> --> 17 <link rel="stylesheet" href="assets/style.css"> 18 <link rel="canonical" href="https://jpda.dev/darksky-to-weatherkit"> 19 <link rel="alternate" type="application/rss+xml" title="jpda.dev" href="https://jpda.dev /feed.xml"> 20 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> 21 <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> 22 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> 23 <link rel="manifest" href="/site.webmanifest"> 24 25 26
26<script type="text/javascript"> 27 var sdkInstance = "appInsightsSDK"; window[sdkInstance] = "appInsights"; var aiName = window[sdkInstance], aisdk = window[aiName] || function (e) { 28 function n(e) { t[e] = function () { var n = arguments; t.queue.push(function () { t[e].apply(t, n) }) } } var t = { config: e }; t.initialize = !0; var i = document, a = window; setTimeout(function () { var n = i.createElement("script"); n.src = e.url || "https://az416426.vo.msecnd.net/next/ai.2.min.js", i.getElementsByTagName("script")[0].parentNode.appendChild(n) }); try { t.cookie = i.cookie } catch (e) { } t.queue = [], t.version = 2; for (var r = ["Event", "PageView", "Exception", "Trace", "DependencyData", "Metric", "PageViewPerformance"]; r.length;)n("track" + r.pop()); n("startTrackPage"), n("stopTrackPage"); var s = "Track" + r[0]; if (n("start" + s), n("stop" + s), n("setAuthenticatedUserContext"), n("clearAuthenticatedUserContext"), n("flush"), !(!0 === e.disableExceptionTracking || e.extensionConfig && e.extensionConfig.ApplicationInsightsAnalytics && !0 === e.extensionConfig.ApplicationInsightsAnalytics.disableExceptionTracking)) { n("_" + (r = "onerror")); var o = a[r]; a[r] = function (e, n, i, a, s) { var c = o && o(e, n, i, a, s); return !0 !== c && t["_" + r]({ message: e, url: n, lineNumber: i, columnNumber: a, error: s }), c }, e.autoExceptionInstrumented = !0 } return t 29 }({ 30 instrumentationKey: "aa50374c-4629-4ab3-a058-5bb4d8bb1924" 31 }); 32 33 window[aiName] = aisdk, aisdk.queue && 0 === aisdk.queue.length && aisdk.trackPageView({}); 34 </script>
34 35 36 37
37<script async defer src="https://buttons.github.io/buttons.js"></script>
37 38</head> 39 40 41 <body> 42 43 <header class="border-bottom-thick px-2 clearfix"> 44 <div class="left sm-width-full py-1 mt-1 mt-lg-0"> 45 <a class="align-middle link-primary text-accent" href="/"> 46 jpda.dev 47 </a> 48 </div> 49 <div class="right sm-width-full"> 50 <ul class="list-reset mt-lg-1 mb-2 mb-lg-1"> 51 52 53 <li class="inline-block"> 54 <a class="align-middle link-primary mr-2 mr-lg-0 ml-lg-2" href="/about"> 55 About 56 </a> 57 </li> 58 59 60 61 62 63 64 65 66 67 68 69 <li class="inline-block"> 70 <a class="align-middle link-primary mr-2 mr-lg-0 ml-lg-2" href="/privacy-policy"> 71 Policies 72 </a> 73 </li> 74 75 76 77 78 79 80 81 82 </ul> 83 </div> 84</header> 85 86 87 <div> 88 <article class="container px-2 mx-auto mb4" itemscope itemtype="http://schema.org/BlogPosting"> 89 <h1 class="h0 col-9 sm-width-full py-4 mt-3 inline-block" itemprop="name headline">DarkSky to WeatherKit: from API keys to signed JWTs</h1> 90 <div class="col-4 sm-width-full mt-1 border-top-thin "> 91 <p class="mb-3 py-2 bold h4"><time datetime="2023-04-20T21:19:08+00:00" itemprop="datePublished">Apr 20, 2023</time></p> 92 </div> 93 94 <div class="prose" itemprop="articleBody"> 95 <p><a href="https://www.youtube.com/watch?v=oo5fg0EfoCY&t=300"><img src="https://img.youtube.com/vi/oo5fg0EfoCY/maxresdefault.jpg" alt="stream video" /></a></p> 96 97<p>As of March 31st, the Dark Sky API is no more, replaced by Appleâs <a href="https://developer.apple.com/weatherkit/">WeatherKit</a>. Iâve used the Dark Sky API as it had a generous free-tier, was easy to use and flexible enough to customize.</p> 98 99<p>Remember a few years ago when customized terminal prompts were all the rage? Pepperidge Farm remembers - especially people like me who probably went a bit overboard.</p> 100 101<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5jue3d8fp25wbzu4z4hh.png" alt="A terminal prompt with too many segments, like weather and stock price" /></p> 102 103<p>Note the weather & stock price. Stock price comes from Yahoo (and follows the same pattern as the weather data), weather data came from Dark Sky. The prompt would read from a cache file.</p> 104 105<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">weatherData</span><span class="o">=</span><span class="si">$(</span>curl <span class="nt">-s</span> <span class="s2">"https://api.darksky.net/forecast/</span><span class="k">${</span><span class="nv">API_KEY</span><span class="k">}</span><span class="s2">/</span><span class="k">${</span><span class="nv">LATLON</span><span class="k">}</span><span class="s2">?exclude=minutely,hourly,daily,alerts,flags"</span><span class="si">)</span> 106<span class="nb">echo</span> <span class="nv">$weatherData</span> <span class="o">></span> <span class="nv">$weatherCacheFile</span> 107</code></pre></div></div> 108 109<p>API key was a static string - could be regenerated on-demand. To run on a trusted machine (like my own), it was no sweat and easy to use. Of course I wouldnât want to distribute that key, but it was for my use locally. It was <em>especially</em> easy to use in shell scripting.</p> 110 111<h2 id="enter-weatherkit">Enter WeatherKit</h2> 112<p>Apple pushed the Dark Sky cutoff date a few times, of course, I missed it and wondered why I was getting 301s and parse error for weather info. On March 31st, the API was dead and Iâd have to migrate over to WeatherKit.</p> 113 114<p>At first glance I figured itâd be pretty similar - this is weather data, after all - but was surprised to see that Appleâs only <a href="https://developer.apple.com/documentation/weatherkitrestapi/request_authentication_for_weatherkit_rest_api">accepted authorization scheme</a> for the API was a bearer token, an EC-signed, asymmetric JWT! Needless to say I was surprised</p> 115 116<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/flwq3m0mzcnjmnje5ds1.png" alt="Surprised Pikachu meme" /></p> 117 118<p>Hereâs an example:</p> 119 120<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> 121 </span><span class="nl">"kid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<KEY ID>
121"</span><span class="p">,</span><span class="w"> 122 </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<TEAM ID>.<SERVICE ID>"</span><span class="p">,</span><span class="w"> 123 </span><span class="nl">"typ"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JWT"</span><span class="p">,</span><span class="w"> 124 </span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ES256"</span><span class="w"> 125</span><span class="p">}</span><span class="err">,</span><span class="w"> 126</span><span class="p">{</span><span class="w"> 127 </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<TEAM ID>"</span><span class="p">,</span><span class="w"> 128 </span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1682008228</span><span class="p">,</span><span class="w"> 129 </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1682011828</span><span class="p">,</span><span class="w"> 130 </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<SERVICE ID>"</span><span class="w"> 131</span><span class="p">}</span><span class="err">.<signature></span><span class="w"> 132</span></code></pre></div></div> 133 134<h2 id="get-all-of-your-pieces">Get all of your pieces</h2> 135 136<p>Sadly, this is not free - unlike the Dark Sky API, this requires an Apple Dev membership. While the WeatherKit API itself has a generous starting tier (500k calls/month) that doesnât have any <em>additional</em> cost, youâll still be out the $99 for the annual membership. Students and non-profits can get it cheaper, so ask around.</p> 137 138<p>Appleâs docs cover most of what youâll need:</p> 139<ul> 140 <li>your Team ID</li> 141 <li>the <code class="language-plaintext highlighter-rouge">.p8</code> file that contains your key</li> 142 <li>the key ID</li> 143 <li>the service ID</li> 144 <li>lat/lon coordinates of a location</li> 145</ul> 146 147<p>The Service ID is a string (like <code class="language-plaintext highlighter-rouge">dev.jpda.terminal-weather</code>) of your choosing.</p> 148 149<h2 id="testing-with-postmans-excellent-jwt-signer">Testing with Postmanâs excellent JWT signer</h2> 150 151<p>To start, Postman has an excellent JWT Bearer auth type that includes an ES256 signer, which makes it much easier to get familiar with the API. I find creating a collection to make this easier (as the authorization settings can be set at the collection level instead of per-request). Here we can configure our token. Choose JWT Bearer as the type, <code class="language-plaintext highlighter-rouge">ES256</code> as the algorithm, get your private key from the .p8 file from Apple.</p> 152 153<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/26a1c21ylrkqxfw66d84.png" alt="Postman authorization screen" /></p> 154 155<h3 id="jwt-payload">JWT Payload</h3> 156 157<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9x42fikmcm1qbyzi8nta.png" alt="JWT payload screen in postman authz" /></p> 158 159<p>The payload is where weâll add our specific claims - there are four we need: <code class="language-plaintext highlighter-rouge">iss</code>,<code class="language-plaintext highlighter-rouge">iat</code> (issued at), <code class="language-plaintext highlighter-rouge">exp</code> (expires on) and <code class="language-plaintext highlighter-rouge">sub</code>ject (who/what the token is about).</p> 160 161<ul> 162 <li>iss: your team ID</li> 163 <li>sub: your service ID</li> 164</ul> 165 166<p>The two timestamps weâll have to sort out. Postman has a <code class="language-plaintext highlighter-rouge">$timestamp</code> global that gives us a unix epoch time (seconds since 1/1/1970) making <code class="language-plaintext highlighter-rouge">iat</code> easy. <code class="language-plaintext highlighter-rouge">exp</code> weâll need calculate off of <code class="language-plaintext highlighter-rouge">iat</code>, which we can do via Pre-request scripts.</p> 167 168<p>Iâve left my information in below as an example:</p> 169 170<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> 171 </span><span class="nl">"iss"</span><span class="p">:</span><span class="s2">"6RT85M24SE"</span><span class="p">,</span><span class="w"> 172 </span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="p">,</span><span class="w"> 173 </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="p">,</span><span class="w"> 174 </span><span class="nl">"sub"</span><span class="p">:</span><span class="s2">"dev.jpda.terminal-weather"</span><span class="w"> 175</span><span class="p">}</span><span class="w"> 176</span></code></pre></div></div> 177 178<p>Pay particular attention to the timestamps - they should <em>not</em> be in quotations, as they are longs. This will complain because <code class="language-plaintext highlighter-rouge">exp_timestamp</code> is not defined, which is OK, weâre going to go define it next.</p> 179 180<h3 id="variables--pre-request-script">Variables & pre-request script</h3> 181 182<p>Weâll need at least one variable to hold the calculated expiration value. I put my coordinates in variables called <code class="language-plaintext highlighter-rouge">HOME_LAT</code> and <code class="language-plaintext highlighter-rouge">HOME_LON</code> since they will be available to the whole collection.</p> 183 184<p>
184Add a variable called <code class="language-plaintext highlighter-rouge">exp_timestamp</code> - it doesnât need an initial value.</p> 185 186<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vpa3umogusl15r9bq9h.png" alt="Variables screen" /></p> 187 188<p>Lastly head into your pre-request scripts tab. Here we can calculate the expiration timestamp like so:</p> 189 190<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">now</span> <span class="o">=</span> <span class="nx">pm</span><span class="p">.</span><span class="nx">variables</span><span class="p">.</span><span class="nx">replaceIn</span><span class="p">(</span><span class="dl">""</span><span class="p">)</span><span class="o">*</span><span class="mi">1</span><span class="p">;</span> 191<span class="kd">var</span> <span class="nx">exp</span> <span class="o">=</span> <span class="nx">now</span> <span class="o">+</span> <span class="mi">3600</span><span class="p">;</span> <span class="c1">// adds +1 hour</span> 192<span class="nx">pm</span><span class="p">.</span><span class="nx">collectionVariables</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">exp_timestamp</span><span class="dl">"</span><span class="p">,</span> <span class="nx">exp</span><span class="p">);</span> 193</code></pre></div></div> 194 195<p>Because of how Postmanâs variables work, <code class="language-plaintext highlighter-rouge">replaceIn</code> is required to use the global $timestamp variable.</p> 196 197<h2 id="jwt-header">JWT Header</h2> 198 199<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hyvzzf7mikuue4negeh.png" alt="JWT Header screen in postman authz" /></p> 200 201<p>Back in the Authorization tab, we have one more item to manage - we need to add two fields to the JWTâs header - the <code class="language-plaintext highlighter-rouge">kid</code> (key ID) and <code class="language-plaintext highlighter-rouge">id</code> - the team ID + service ID of your app. This is under âadvanced configuration.â We can add our claims and postman will handle adding claims like <code class="language-plaintext highlighter-rouge">alg</code> for the algorithm and <code class="language-plaintext highlighter-rouge">type</code>.</p> 202 203<p>Iâve left my values in as an example:</p> 204 205<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> 206 </span><span class="nl">"kid"</span><span class="p">:</span><span class="s2">
206"PMV7JU5DJJ"</span><span class="p">,</span><span class="w"> 207 </span><span class="nl">"id"</span><span class="p">:</span><span class="s2">"6RT85M24SE.dev.jpda.terminal-weather"</span><span class="w"> 208</span><span class="p">}</span><span class="w"> 209</span></code></pre></div></div> 210 211<h2 id="ready-lets-get-the-weather-right">Ready! Letâs get the weatherâ¦! Right?</h2> 212 213<p>OK, at long last we are ready to get the temperature! Well, not quite. First we need to see what datasets are available for your location.</p> 214 215<p>Get a new request (making sure to save it/add it to your collection we just configured for authorization) and drop in this url:</p> 216 217<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://weatherkit.apple.com/api/v1/availability//?country=US 218</code></pre></div></div> 219 220<p>Make sure your authorization tab is configured for âinherit from parentâ and you have variables set for <code class="language-plaintext highlighter-rouge">and</code> and update your country if necessary. Run this and, if all went well, we should get a response with an array of the available datasets for that location.</p> 221 222<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w"> 223 </span><span class="s2">"currentWeather"</span><span class="p">,</span><span class="w"> 224 </span><span class="s2">"forecastDaily"</span><span class="p">,</span><span class="w"> 225 </span><span class="s2">"forecastHourly"</span><span class="p">,</span><span class="w"> 226 </span><span class="s2">"forecastNextHour"</span><span class="p">,</span><span class="w"> 227 </span><span class="s2">"weatherAlerts"</span><span class="w"> 228</span><span class="p">]</span><span class="w"> 229</span></code></pre></div></div> 230 231<p>If you get an error, take the generated JWT (from the headers tab of the request) and drop it in your favorite JWT decoder (like jwt.io) and check the contents.</p> 232 233<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot4j3gzof5paq67eytct.png" alt="get authz header" /></p> 234 235<p>Note that these may be out-of-date, to get the specific token used in the failed request, look in the Postman console (View â> Show Console).</p> 236 237<h2 id="ok-so-now-we-can-get-the-weather">Ok so <em>NOW</em> we can get the weather?</h2> 238 239<p>
239At long last, now that we know whatâs available, <code class="language-plaintext highlighter-rouge">currentWeather</code> should give us what weâre looking for. Create a new request in Postman in this collection and letâs apply what weâve determined:</p> 240 241<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://weatherkit.apple.com/api/v1/weather/en//?dataSets=currentWeather&country=US 242</code></pre></div></div> 243 244<p>Note the <code class="language-plaintext highlighter-rouge">/en/</code> in the path - use the ISO country code for your region. I am only interested in current weather, so thatâs the dataset Iâll request.</p> 245 246<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> 247 </span><span class="nl">"currentWeather"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 248 </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CurrentWeather"</span><span class="p">,</span><span class="w"> 249 </span><span class="nl">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> 250 </span><span class="nl">"attributionURL"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://weatherkit.apple.com/legal-attribution.html"</span><span class="p">,</span><span class="w"> 251 </span><span class="nl">"expireTime"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-04-20T21:20:01Z"</span><span class="p">,</span><span class="w"> 252 </span><span class="nl">"latitude"</span><span class="p">:</span><span class="w"> </span><span class="mf">42.860</span><span class="p">,</span><span class="w"> 253 </span><span class="nl">"longitude"</span><span class="p">:</span><span class="w"> </span><span class="mf">-82.590</span><span class="p">,</span><span class="w"> 254 </span><span class="nl">"readTime"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-04-20T21:15:01Z"</span><span class="p">,</span><span class="w"> 255 </span><span class="nl">"reportedTime"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-04-20T20:02:16Z"</span><span class="p">,</span><span class="w"> 256 </span><span class="nl">"units"</span><span class="p">:</span><span class="w"> </span><span class="s2">"m"</span><span class="p">,</span><span class="w"> 257 </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> 258 </span><span class="p">},</span><span class="w"> 259 </span><span class="nl">"asOf"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-04-20T21:15:01Z"</span><span class="p">,</span><span class="w"> 260 </span><span class="nl">"cloudCover"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.65</span><span class="p">,</span><span class="w"> 261 </span><span class="nl">"cloudCoverLowAltPct"</span><span class="p">:</span><span class="w"> </span><span class="mf">
2610.03</span><span class="p">,</span><span class="w"> 262 </span><span class="nl">"cloudCoverMidAltPct"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.66</span><span class="p">,</span><span class="w"> 263 </span><span class="nl">"cloudCoverHighAltPct"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.34</span><span class="p">,</span><span class="w"> 264 </span><span class="nl">"conditionCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MostlyCloudy"</span><span class="p">,</span><span class="w"> 265 </span><span class="nl">"daylight"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> 266 </span><span class="nl">"humidity"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.55</span><span class="p">,</span><span class="w"> 267 </span><span class="nl">"precipitationIntensity"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w"> 268 </span><span class="nl">"pressure"</span><span class="p">:</span><span class="w"> </span><span class="mf">1013.66</span><span class="p">,</span><span class="w"> 269 </span><span class="nl">"pressureTrend"</span><span class="p">:</span><span class="w"> </span><span class="s2">"falling"</span><span class="p">,</span><span class="w"> 270 </span><span class="nl">"temperature"</span><span class="p">:</span><span class="w"> </span><span class="mf">18.98</span><span class="p">,</span><span class="w"> 271 </span><span class="nl">"temperatureApparent"</span><span class="p">:</span><span class="w"> </span><span class="mf">18.33</span><span class="p">,</span><span class="w"> 272 </span><span class="nl">"temperatureDewPoint"</span><span class="p">:</span><span class="w"> </span><span class="mf">9.65</span><span class="p">,</span><span class="w"> 273 </span><span class="nl">"uvIndex"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> 274 </span><span class="nl">"visibility"</span><span class="p">:</span><span class="w"> </span><span class="mf">30544.32</span><span class="p">,</span><span class="w"> 275 </span><span class="nl">"windDirection"</span><span class="p">:</span><span class="w"> </span><span class="mi">147</span><span class="p">,</span><span class="w"> 276 </span><span class="nl">"windGust"</span><span class="p">:</span><span class="w"> </span><span class="mf">28.80</span><span class="p">,</span><span class="w"> 277 </span><span class="nl">"windSpeed"</span><span class="p">:</span><span class="w"> </span><span class="mf">16.96</span><span class="w"> 278 </span><span class="p">}</span><span class="w"> 279</span><span class="p">}</span><span class="w"> 280</span></code></pre></div></div> 281 282<p>Success! Itâs all in metric units, and the docs seem to indicate thatâs what it is and youâll like it. So a bit of work to convert from Cs to Fs (or Ks if youâre adventurous).</p> 283 284<h2 id="now-what">Now what?</h2> 285 286<p>Now that we can at least <em>get</em> the data again, how are we going to leverage it? Remember I was using this in my terminal - using <code class="language-plaintext highlighter-rouge">curl</code> to go fetch the data with an environment variable with the API key. Whatâs best here?</p> 287 288<p>Whatever we use is going to need to access the private key - meaning whatever is generating those JWTs will need to be trusted - local, remote, etc.</p> 289 290<h3 id="local-executable">Local executable</h3> 291 292<p>This is pretty straightforward, could easily balance JWT lifetime with overhead. Perhaps once-per-day with a 24-hour JWT? I wouldnât normally advocate for long-lived tokens but as itâs locally generated, and itâs relatively low-risk data, it seems like a decent tradeoff.</p> 293 294<h3 id="remote">Remote</h3> 295 296<p>Something remote sounds neat too - like a Lambda or Azure Function - but once we leave our trusted space, weâll have to figure out how only the correct clients can get tokens to call the weather service. Something like AAD, Okta, Auth0, etc to secure the API to get JWTs. Which, at that point, maybe is easier to just let the function/etc get the weather data itself <em>and cache it</em>, further reducing the number of paid API calls to WeatherKit. Something like:</p> 297 298<p>Client â> (secured via cloud auth service) http proxy API (keeps Apple private key + generates JWTs) â> WeatherKit</p> 299 300<p>This is, in fact, a lot like the BFF pattern (backends for frontends) used for things like storing secrets securely, caching API responses, etc.</p> 301 302<p>Remote has tradeoffs, of course, like hosting, downtime, etc. And itâs one more thing to break :D</p> 303 304<h2 id="next-well-pick">Next, weâll pick!</h2> 305 306<p><a href="https://aka.wtf/twitch">Join me on Twitch</a> and weâll decide whatâs next!</p> 307 308 </div> 309 310</article> 311 312<div class="container mx-auto px-2 py-2 clearfix"> 313 <!-- Use if you want to show previous and next for all posts. --> 314 315 316 317 <div class="col-4 sm-width-full left mr-lg-4 mt-3"> 318 <a class="no-underline border-top-thin py-1 block" href="https://jpda.dev/gcc-mid-on-prem-data-gateway">
319 <span class="h5 bold text-accent">Previous</span> 320 <p class="bold h3 link-primary mb-1">Hacking on the On-prem data gateway for GCC-mid accounts</p> 321 <p>I had an interesting one come across my desk here at WTF HQ earlier this week - before I knew...</p> 322 </a> 323 </div> 324 325 326 327</div> 328 329 </div> 330 331 <div class="border-top-thin clearfix mt-2 mt-lg-4"> 332 <div class="container mx-auto px-2"> 333 <p class="col-8 sm-width-full left py-2 mb-0">This project is maintained by <a class="text-accent" href="https://github.com/jpda">jpda</a></p> 334 <ul class="list-reset right clearfix sm-width-full py-2 mb-2 mb-lg-0"> 335 <li class="inline-block mr-1"> 336 <a href="https://twitter.com/share" class="twitter-share-button" data-hashtags="jpda.dev">Tweet</a>
336<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
336 337 </li> 338 <li class="inline-block"> 339 <a class="github-button" href="https://github.com/jpda/jpda.github.io" data-icon="octicon-star" data-count-href="jpda//stargazers" data-count-api="/repos/jpda/#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star jpda/ on GitHub">Star</a> 340 </li> 341 </ul> 342 </div> 343</div> 344 345 346 </body> 347 348</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.