<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Harry Adel</title>
    <description>The latest articles on Forem by Harry Adel (@harryadel).</description>
    <link>https://forem.com/harryadel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F229383%2F2c01590d-a8d9-4cb6-aa40-58b8eea91433.png</url>
      <title>Forem: Harry Adel</title>
      <link>https://forem.com/harryadel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/harryadel"/>
    <language>en</language>
    <item>
      <title>The Meteor 3.0 Migration: A Space Exploration Mission</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Tue, 18 Mar 2025 16:05:29 +0000</pubDate>
      <link>https://forem.com/meteor/the-meteor-30-migration-a-space-exploration-mission-3gb5</link>
      <guid>https://forem.com/meteor/the-meteor-30-migration-a-space-exploration-mission-3gb5</guid>
      <description>&lt;p&gt;Just wanted to share this story of how we completed our Meteor 3.0 migration at my company &lt;a href="https://www.medbeauty.com/" rel="noopener noreferrer"&gt;medbeauty&lt;/a&gt; and how more than a year's worth of effort is coming to an end with a PR of 439 file changes about to be merged 🤞&lt;/p&gt;

&lt;h2&gt;
  
  
  Mission Briefing: The Launch Preparation
&lt;/h2&gt;

&lt;p&gt;A little background on the application: it was started in 2014. In my humble opinion, it's one of the last OG Meteor fortresses as it still utilizes Blaze &amp;amp; Cordova. Full-stack reactivity all the way thanks to &lt;a href="https://github.com/dburles/meteor-collection-helpers" rel="noopener noreferrer"&gt;collection-helpers&lt;/a&gt;. It's about as OG as you can get. &lt;/p&gt;

&lt;h2&gt;
  
  
  Liftoff: Initial Trajectory Calculations
&lt;/h2&gt;

&lt;p&gt;The team at medbeauty had already dipped their toes in the 3.0 waters as their migration efforts began in 2023 while I joined them late January 2024. They had migrated lots of bits to async. Even &lt;a href="https://github.com/danieldornhardt" rel="noopener noreferrer"&gt;DanielDornhardt&lt;/a&gt; had created a magnificent &lt;a href="https://forums.meteor.com/t/blaze-async-migration-helper-babel-plugin-tracker-async-enhance-for-the-client-side-released/60842" rel="noopener noreferrer"&gt;babel-plugin&lt;/a&gt; (which isn't getting the credit it deserves, by the way) to help keep your Blaze code looking nice while migrating it to async.&lt;/p&gt;

&lt;p&gt;Since I had already been involved in the Meteor community, I had been getting notifications in my email with the PRs that were popping up here and there, but didn't see anything materialize. The core team was hammering away at the core packages, but no love was being shown to Atmosphere packages. After all, a framework is only as good as its surrounding ecosystem—no wonder people were ditching Blaze for React!&lt;/p&gt;

&lt;p&gt;I was already skeptical of such an endeavor and didn't even know if it was feasible. So I started with what I know best: Collection2!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Launch Sequence: Igniting Collection2
&lt;/h2&gt;

&lt;p&gt;I believe Collection2 is one of those very few packages that act as a cornerstone for the entire Meteor framework. If these packages could be made async-friendly, then the rest would surely follow.&lt;/p&gt;

&lt;p&gt;A little side trivia: I never anticipated nor vowed to undertake maintaining Collection2. Back when MCP was just getting started, I created an &lt;a href="https://github.com/Meteor-Community-Packages/organization/issues/29" rel="noopener noreferrer"&gt;issue&lt;/a&gt; suggesting including the package in the roster due to its importance and the fact that Eric/&lt;a href="https://github.com/aldeed" rel="noopener noreferrer"&gt;Aldeed&lt;/a&gt; had already moved beyond Meteor. Once the package was moved, I was surprised that StorytellerCZ granted me maintainer access. In hindsight, it was a great decision ;)&lt;/p&gt;

&lt;p&gt;Back to Collection2, work had already &lt;a href="https://github.com/Meteor-Community-Packages/meteor-collection2/pull/443" rel="noopener noreferrer"&gt;started&lt;/a&gt; thanks to &lt;a href="https://github.com/klablink" rel="noopener noreferrer"&gt;Klablink&lt;/a&gt;—big shoutout to him as he helped with many packages. I started a PR &lt;a href="https://github.com/Meteor-Community-Packages/meteor-collection2/pull/444" rel="noopener noreferrer"&gt;building&lt;/a&gt; on his work and went from there. Bit by bit, I began to understand the changes made and even tried to improve it and cut down on the code changes. This was also the first time in my life where I had released a beta version of Meteor packages (I didn't know if it was even possible, but hey, we keep on learning).&lt;/p&gt;

&lt;p&gt;I kept modifying code &amp;amp; releasing betas, then modifying the package in our application to see if anything would break. Also, Andrei/&lt;a href="https://github.com/alisnic" rel="noopener noreferrer"&gt;alisnic&lt;/a&gt; came to the &lt;a href="https://github.com/Meteor-Community-Packages/meteor-collection2/pull/444#issuecomment-1870149278" rel="noopener noreferrer"&gt;rescue&lt;/a&gt;. With 2400 unit tests and 821 e2e test cases, surely I didn't break anything, right?&lt;/p&gt;

&lt;p&gt;After a couple of hiccups and many commits, we made it! &lt;a href="https://github.com/Meteor-Community-Packages/meteor-collection2/pull/444#issuecomment-1898346301" rel="noopener noreferrer"&gt;4.0.0&lt;/a&gt; was released, affirming the possibility of not only migrating Meteor packages—but in my own eyes—Meteor and Meteor projects as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entering Orbit: Community Navigation Challenges
&lt;/h2&gt;

&lt;p&gt;This experience had not only proven the possibility but also revealed the amount of work that awaited us. It highlighted a problem with the coordination of the community because I had seen people not only replicating work but also migrating silently or forking packages entirely! Which could bring an end to this unborn endeavor entirely!&lt;/p&gt;

&lt;p&gt;That's why after brief discussions with Daniel, we came up with the idea to document the packages that needed to be migrated and their current status. This eventually led to the birth of this forum &lt;a href="https://forums.meteor.com/t/looking-for-help-migrating-packages-to-meteor-3-0/60985" rel="noopener noreferrer"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The forum post helped to showcase who was working on what and gave a sense of how things were moving. While I had hoped for more widespread community participation to distribute the workload, the reality was that the effort largely fell to a dedicated group of familiar faces along with a few new contributors. When I shared my thoughts with &lt;a href="https://github.com/alimgafar" rel="noopener noreferrer"&gt;alimgafar&lt;/a&gt; about wanting more community involvement, he offered a perspective that resonated with me: "that's how open source™ works" – a small group of passionate contributors often carries the weight of major transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ymwj0u0x6azzil8n7kh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ymwj0u0x6azzil8n7kh.png" alt="Jan brothers" width="770" height="978"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Needless to say, medbeauty &amp;amp; Daniel were both gracious with their time and monetary support. At one point, Daniel even instructed me to take a pause and &lt;a href="https://forums.meteor.com/t/looking-for-help-migrating-packages-to-meteor-3-0/60985/39?u=harry97" rel="noopener noreferrer"&gt;create PRs&lt;/a&gt; for the packages that we had migrated so others could benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gravitational Anomalies: Deployment Mysteries
&lt;/h2&gt;

&lt;p&gt;As we migrated more and more packages and made significant improvements to our codebase, we became increasingly confident in our ability to increase the version number.&lt;/p&gt;

&lt;p&gt;One of the most intriguing things that stood out to me during this phase was that we ran into troubles when we tried building and deploying our Meteor application. The problem had already been &lt;a href="https://github.com/meteor/meteor/issues/12932" rel="noopener noreferrer"&gt;reported&lt;/a&gt; by &lt;a href="https://github.com/zodern" rel="noopener noreferrer"&gt;zodern&lt;/a&gt;, and surprisingly, we were the third to comment on it!&lt;/p&gt;

&lt;p&gt;By that time, I had read countless articles here on the forums about how people had migrated to the lush green 3.0 lands, yet I was left puzzled. If so many teams had successfully made the journey, I wondered why more weren't encountering this deployment issue. I began to question whether there was a difference between production-grade applications with paying customers versus smaller projects with less complex requirements. The contrast between reported experiences and our own challenges made me curious about what we might be missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Course Corrections: Navigation Adjustments
&lt;/h2&gt;

&lt;p&gt;Migrating packages was no easy task. At times, we had to make course corrections and prioritize getting things working instead of getting things right.&lt;/p&gt;

&lt;p&gt;I had to make adjustments and make the best of the current situation, which is why I opted to still use node-sass in fourseven:scss instead of switching to &lt;a href="https://github.com/leonardoventurini/meteor-scss" rel="noopener noreferrer"&gt;leonardoventurini:scss&lt;/a&gt;. I had &lt;a href="https://github.com/Meteor-Community-Packages/meteor-scss/pull/321#issue-2307741493" rel="noopener noreferrer"&gt;recommended&lt;/a&gt; forking and modifying a core package just to make things work.&lt;/p&gt;

&lt;p&gt;But this experience didn't come easily. I had burned weeks on end trying to perfect the packages we're using. I had to freeze a version of collection-hooks that's compatible with our code. I commented out pieces of code that utilized autoValue, which still is &lt;a href="https://github.com/Meteor-Community-Packages/meteor-simple-schema/pull/746" rel="noopener noreferrer"&gt;in the works&lt;/a&gt;. And I think that's the moral of the story I want to drive home: it takes time, there's no perfect flight path, and you have to make some navigational adjustments.&lt;/p&gt;

&lt;p&gt;Thankfully, many commits later, our application is stable and deployed to production, but there's still remaining work for us as we're still facing &lt;a href="https://github.com/meteor/meteor/issues/13474" rel="noopener noreferrer"&gt;problems&lt;/a&gt; with Cordova. We're currently considering our options of soliciting outside help, so maybe there will be a part 2?&lt;/p&gt;

&lt;h2&gt;
  
  
  Mission Data: Exploration Statistics
&lt;/h2&gt;

&lt;p&gt;Did you like my storytelling talents? Call me StorytellerEG — because clearly the pyramids make for better storytelling backdrops than Prague Castle!&lt;/p&gt;

&lt;p&gt;Now time for some fun mission statistics for those interested:&lt;/p&gt;

&lt;p&gt;A picture is worth 1000 words:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwi71qcz23343kpaufxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwi71qcz23343kpaufxz.png" alt="tiny PR" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;250 commits (tens of them were trying to re-run the CI actually)&lt;/li&gt;
&lt;li&gt;439 changed files (200 forked packages?)&lt;/li&gt;
&lt;li&gt;2 Conversations (I'm a man of few words)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, we had started and closed multiple PRs in the hopes of merging 3.0, so this is probably the 3rd clone with plenty of merged/squashed commits with multiple mini-PRs already merged in, so the amount of code we changed is larger than what's mentioned in this image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mission Control's Transmission: Guidance for Future Explorers
&lt;/h2&gt;

&lt;p&gt;Since I grew a single grey hair during this migration, this warrants me transmitting wisdom to all you young space cadets out there on how to migrate your applications. So ditch your flight manuals and come listen to how seasoned astronauts do it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Your Life Support Systems&lt;/strong&gt;: Time and time again, I've been proven that if you were to boil down all of the best practices of software development, it just comes down to having tests. If you don't have a robust test suite with plenty of coverage, then you might want to start investing in it immediately. Otherwise, suck to be you, sorry 🤷‍♂️ &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don't Jump to Warp Speed Immediately&lt;/strong&gt;: Don't kick off your application by pumping it to 3.0 like I did. I thought, "Hey, I'd just increase the version number and go from there fixing errors bit by bit and getting it working." WRONG!&lt;/p&gt;

&lt;p&gt;Again, I learned this the hard way. I tried and failed, or better yet, didn't even get to try because as I attempted to run &lt;code&gt;meteor update --release 3.x&lt;/code&gt;, it crashed with the following &lt;a href="https://github.com/meteor/meteor/issues/12902" rel="noopener noreferrer"&gt;error&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MINISAT-out: Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value 67108864, (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.
MINISAT-err: Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value 67108864, (2) compile with ALLOW_MEMORY_GROWTH which adjusts the size at runtime but prevents some optimizations, or (3) set Module.TOTAL_MEMORY before the program runs.
abort() at Error                              
    at jsStackTrace (packages/logic-solver.js:21:18626)
    at stackTrace (packages/logic-solver.js:21:18809)
    at abort (packages/logic-solver.js:51:28956)
    at enlargeMemory (packages/logic-solver.js:21:19142)
    at Function.dynamicAlloc [as alloc] (packages/logic-solver.js:21:7927)
    at _sbrk (packages/logic-solver.js:21:58803)
    at Sd (packages/logic-solver.js:25:98389)
    at Ud (packages/logic-solver.js:25:109800)
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This is what pushed me into dissecting this into small pieces and starting this whole 3.0 libraries migration thread.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install Your Warning System&lt;/strong&gt;: Enable the &lt;code&gt;WARN_WHEN_USING_OLD_API&lt;/code&gt; env variable. It's a little env variable that got added during &lt;a href="https://guide.meteor.com/2.12-migration" rel="noopener noreferrer"&gt;2.12&lt;/a&gt; but didn't get much attention. Enabling it should be your first pre-flight check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gradual Engine Upgrades&lt;/strong&gt;: Once you've enabled it, slowly start migrating all of your old DB calls to async.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replace Components Methodically&lt;/strong&gt;: As you're doing the previous step, you might need to start swapping out the old Meteor packages with the new ones. Act accordingly and, most importantly, &lt;em&gt;patiently&lt;/em&gt;. You don't want to introduce too many changes at once; that's how you crash your spacecraft. Don't ask how I know.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple Small Launches Beat One Big One&lt;/strong&gt;: Introduce changes in small, separate PRs as much as you can. We migrated all our packages and DB calls before launching into the final frontier—err, 3.0, I mean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Share Your Flight Log&lt;/strong&gt;: Share your experience and stay in touch. It helped me immensely that I was cross-referencing many ideas with the community and reporting any issues back to mission control. In a way, this article is part of this migration journey so we can collectively learn and elevate each other.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Mission Accomplished: Return to Earth
&lt;/h2&gt;

&lt;p&gt;After more than a year of work, our Meteor 3.0 migration is complete and running in production. This achievement wouldn't have been possible without the support of the open source community.&lt;/p&gt;

&lt;p&gt;Special thanks to &lt;a href="https://github.com/klablink" rel="noopener noreferrer"&gt;Klablink&lt;/a&gt;, &lt;a href="https://github.com/bhunjadi" rel="noopener noreferrer"&gt;bhunjadi&lt;/a&gt;, &lt;a href="https://github.com/alisnic" rel="noopener noreferrer"&gt;alisnic&lt;/a&gt;, &lt;a href="https://github.com/bratelefant" rel="noopener noreferrer"&gt;bratelefant&lt;/a&gt;, &lt;a href="https://github.com/vparpoil" rel="noopener noreferrer"&gt;vparpoil&lt;/a&gt;, &lt;a href="https://github.com/leonardoventurini" rel="noopener noreferrer"&gt;leonardoventurini&lt;/a&gt;, &lt;a href="https://github.com/dr-dimitru" rel="noopener noreferrer"&gt;dr-dimitru&lt;/a&gt;, and &lt;a href="https://github.com/paulincai" rel="noopener noreferrer"&gt;paulishca&lt;/a&gt; who contributed critical components to this ecosystem-wide effort. I apologize if I forgot any names.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96v1jqjvhryv4lnjygr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96v1jqjvhryv4lnjygr7.png" alt="Your #1 Meteor expert" width="500" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like help migrating to 3.0 or generally would like to connect and chat about your own space mission, I've set up a &lt;a href="https://cal.com/harryadel" rel="noopener noreferrer"&gt;cal&lt;/a&gt; account.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Apple Sign-In using Hapijs</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Sat, 30 Mar 2024 13:32:01 +0000</pubDate>
      <link>https://forem.com/harryadel/apple-sign-in-using-hapijs-32ik</link>
      <guid>https://forem.com/harryadel/apple-sign-in-using-hapijs-32ik</guid>
      <description>&lt;p&gt;Recently, I've been tasked with enabling Apple Sign-In using Hapijs but the content online that relates to this part is very scarce. So, I've decided to come up with my own little tutorial that connects everything together from the start to the very end.&lt;/p&gt;

&lt;p&gt;This tutorial is split up into two parts, the first is about configuring your Apple developer account and getting the required credentials while the second part is how to use these credentials to set up the authentication flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 1
&lt;/h1&gt;

&lt;p&gt;We need to attain the following credentials from Apple:&lt;/p&gt;

&lt;h3&gt;
  
  
  Team/App ID
&lt;/h3&gt;

&lt;p&gt;Apple lets your create an application that encompasses all of your services. It might seem like an over kill but it makes sense when you've an application that has multiple fronts like web, IOS or Vision OS, you name it. It's one huge umbrella for all of your "team" needs. The biggest takeaway here is to write down the Team ID you get when you create an application and also be sure to configure it to allow Apple sign-in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgszlpwaaiiyrng7ivtb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgszlpwaaiiyrng7ivtb.jpg" alt="Register an App" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytj8xeidg1x9lpgsle2s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytj8xeidg1x9lpgsle2s.jpg" alt="Check sign in with Apple" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Service/Client ID
&lt;/h3&gt;

&lt;p&gt;Now, we're going to create a services or a client that initiates such login attempts. As an example, I'm going to name our application "devtools" and the domain for it is "devtools.io". Apple is going to require you to provide an identifier for your service and it's customary to enter in your domain reversed, in our case "io.devtools". This is going to be our Client ID, so don't forget to write it down. Keep in mind, Apple doesn't allow entering localhost so put down any name and later in this tutorial, I'll show you to test things locally easily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ergu797pqww4iarq2l7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ergu797pqww4iarq2l7.jpg" alt="Image description" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx98z8s7dcfhwk4yiye52.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx98z8s7dcfhwk4yiye52.jpg" alt="Image description" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5sodf27q6shovu6hyg1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5sodf27q6shovu6hyg1.jpg" alt="Image description" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3od6reb81jmip2wlfda.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3od6reb81jmip2wlfda.jpg" alt="Image description" width="787" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key ID
&lt;/h3&gt;

&lt;p&gt;Finally, Apple requires you to use a private key to sign your communications with it for an extra layer of protection. You're going to download a p8 file that we're going to use later on. Don't forget to write down the Key ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqtw51fl8q4vjq0d6n46.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqtw51fl8q4vjq0d6n46.jpg" alt="Image description" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fph3zi866c3u0kkntmk80.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fph3zi866c3u0kkntmk80.jpg" alt="Image description" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1fkga9omniib8sqocyi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1fkga9omniib8sqocyi.jpg" alt="Image description" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you're done with part you should end up with the following env variables: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;APPLE_CLIENTID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;APPLE_TEAMID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;APPLE_KEYID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;APPLE_CALLBACK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the &lt;code&gt;.p8&lt;/code&gt; key file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 2
&lt;/h1&gt;

&lt;p&gt;Before diving into the code, there're few adjustments we need to make in order to be able to run our application locally on HTTPs and to also route to the proper domain name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating locally signed SSL certificates
&lt;/h3&gt;

&lt;p&gt;We're going to create self signed certificates in order to be able to use them in our application and place them in a directory called &lt;code&gt;certs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -x509 -out localhost.crt -keyout localhost.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj "/CN=localhost" -extensions EXT -config &amp;lt;( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Host configuration
&lt;/h3&gt;

&lt;p&gt;Go ahead and edit &lt;code&gt;/etc/hosts&lt;/code&gt; so that devtools.io routes to our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;127.0.0.1  devtools.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our &lt;code&gt;index.js&lt;/code&gt;, use our locally signed certificates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Hapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Default HTTPS port&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;certs/localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;certs/localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might have to use &lt;code&gt;sudo&lt;/code&gt; to run your application. Now fire up your browser and visit devtools.io, and you'd be met with something that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisyvrk2uwkh9twoywkle.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fisyvrk2uwkh9twoywkle.jpg" alt="Localhost" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type in &lt;code&gt;thisisunsafe&lt;/code&gt; in your browser so that chrome'd allow to visit the website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Apple Strategy
&lt;/h3&gt;

&lt;p&gt;The meaty part which you've been looking for. Apologies if this part is messy but it was no easy task. But I'll try to explain things to the best of my ability.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;profile&lt;/code&gt; function is triggered after a successful authorization/token request. Because Apple is the cool company we all know and love, to verify the &lt;code&gt;id_token&lt;/code&gt; it sends back you have to fetch a &lt;em&gt;list&lt;/em&gt; of public keys and match it with key ID (&lt;code&gt;kid&lt;/code&gt;) in header of the received &lt;code&gt;token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then use &lt;a href="https://www.npmjs.com/package/jwk-to-pem"&gt;jwk-to-pem&lt;/a&gt; to convert the public key into something &lt;a href="https://www.npmjs.com/package/jsonwebtoken"&gt;jsonwebtoken&lt;/a&gt; can use to verify the signature. &lt;/p&gt;

&lt;p&gt;Also, you'd find that I parse a property called credentials since I asked for the user's email and name in &lt;code&gt;scope&lt;/code&gt; but it's something I had to monkey patch into the library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hapijs/bell/pull/494"&gt;https://github.com/hapijs/bell/pull/494&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://appleid.apple.com/auth/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://appleid.apple.com/auth/token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oauth2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;useParamsAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;applePublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://appleid.apple.com/auth/keys`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rsaKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;applePublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;decodedToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kid&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;jwkToPem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rsaKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;decodedToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
            &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Token verification failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We finally get to use the &lt;code&gt;.p8&lt;/code&gt; key Apple gave us at the very beginning to sign our token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./secret_key/AuthKey_XSYG2M3R.p8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSecretKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_TEAMID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://appleid.apple.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_CLIENTID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;signOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ES256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// you must use this algorithm, not jsonwebtoken's default&lt;/span&gt;
    &lt;span class="na"&gt;keyid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_KEYID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_CLIENTID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getSecretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;forceHttps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isSecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;providerParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;response_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form_post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie_encryption_password_secure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_CALLBACK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's very crucial to add this piece of code as bell is unable to route you properly back to the application. Shout out to &lt;a href="https://github.com/hapijs/bell/issues/462#issuecomment-812033842"&gt;annahassel&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fixAppleCallbackForBell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/apple&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/auth/apple?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onRequest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fixAppleCallbackForBell&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's pretty much it. Let me know if you've any questions.&lt;/p&gt;




&lt;p&gt;This article wouldn't have been possible without the knowledge shared in these articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codeculturepro.medium.com/how-to-implement-login-with-apple-in-node-js-7a709969f89d"&gt;https://codeculturepro.medium.com/how-to-implement-login-with-apple-in-node-js-7a709969f89d&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@artyomefremov/add-sign-in-with-apple-button-to-your-website-today-part-1-12ed1444623a"&gt;https://medium.com/@artyomefremov/add-sign-in-with-apple-button-to-your-website-today-part-1-12ed1444623a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sarunw.com/posts/sign-in-with-apple-1/"&gt;https://sarunw.com/posts/sign-in-with-apple-1/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple"&gt;https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeculturepro.medium.com/how-to-implement-login-with-apple-in-node-js-7a709969f89d"&gt;https://codeculturepro.medium.com/how-to-implement-login-with-apple-in-node-js-7a709969f89d&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.devgenius.io/how-to-implement-apple-login-with-nestjs-in-seconds-b88f05abe847"&gt;https://blog.devgenius.io/how-to-implement-apple-login-with-nestjs-in-seconds-b88f05abe847&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hapijs/bell/issues/462"&gt;https://github.com/hapijs/bell/issues/462&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/75479798/how-to-setup-apple-sign-in-using-hapis-bell"&gt;https://stackoverflow.com/questions/75479798/how-to-setup-apple-sign-in-using-hapis-bell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Researching and sharing knowledge in this article took quite a considerable amount of time. I'd appreciate if you can sponsor me on &lt;a href="https://github.com/sponsors/harryadel/"&gt;Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>hapijs</category>
    </item>
    <item>
      <title>Genesis of a Framework: Unveiling the Meteor Story</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Fri, 23 Feb 2024 16:23:55 +0000</pubDate>
      <link>https://forem.com/meteor/genesis-of-a-framework-unveiling-the-meteor-story-50dd</link>
      <guid>https://forem.com/meteor/genesis-of-a-framework-unveiling-the-meteor-story-50dd</guid>
      <description>&lt;p&gt;Given Meteor has been around for almost 11 years now, I think it's important to document its early beginnings and origin for the future generation of developers and there's no one better to tell us about Meteor than one of its original creators -- Geoff Schmidt! &lt;/p&gt;

&lt;p&gt;What made me feel that this is even more important to document is the scarcity of information about Meteor initial stages illustrated by this forum &lt;a href="https://forums.meteor.com/t/history-of-meteor/15252/5" rel="noopener noreferrer"&gt;thread&lt;/a&gt; that has resurfaced during categorization of old threads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn94bcjgcu9advd420x2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn94bcjgcu9advd420x2.jpg" alt="History_of_Meteor.jpg" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've come to know about many fascinating stories of Meteor's inception through Geof Schmidt old &lt;a href="https://archive.is/EruEe" rel="noopener noreferrer"&gt;Quora account&lt;/a&gt; and today I'd like to share some of the best stories about Meteor's first years.&lt;/p&gt;

&lt;p&gt;Table of contents: &lt;br&gt;
        - How Meteor creators met&lt;br&gt;
        - Differences between Meteor and Luna&lt;br&gt;
        - Other possible Meteor names&lt;br&gt;
        - Meteor pros in the eyes of its creators&lt;br&gt;
        - Meteor original website design&lt;br&gt;
    - Conclusion&lt;/p&gt;

&lt;h2&gt;
  
  
  How Meteor creators met
&lt;/h2&gt;

&lt;p&gt;How a little talk during a holiday party followed by a visit and an email later led to the recruitment of David Greenspan (Etherpad creator).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://archive.ph/NH2ke" rel="noopener noreferrer"&gt;How did David Greenspan (Etherpad creator) team up with Geoff Schmidt (Asana employee #9, worked on Luna) and team to form Meteor?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Differences between Meteor and Luna
&lt;/h2&gt;

&lt;p&gt;Probably the most intriguing fact about Meteor is that it's based on another Framework called "Luna" used at Asana and how Meteor inverts the same principle.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://archive.ph/S3Pbk" rel="noopener noreferrer"&gt;Web Application Frameworks: What are the differences between Meteor and Luna?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other possible Meteor names
&lt;/h2&gt;

&lt;p&gt;Ever wondered why Meteor creators chose such a name? Did you now that Polymer was one of the potential names?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://archive.is/3nIXJ" rel="noopener noreferrer"&gt;Is "Meteor" (web framework) a pun on Luna or Comet?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Meteor pros in the eyes of its creators
&lt;/h2&gt;

&lt;p&gt;You can ask any hardcore developer on the official Meteor forums about why you should go with Meteor and you'd get a pretty solid answer but it's always interesting to me to know what the original creators liked about Meteor and what type of problems were they attempting to solve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://archive.ph/F4UHV#selection-263.0-263.99" rel="noopener noreferrer"&gt;In lay terms for someone with only basic software development knowledge, what is cool about Meteor?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Meteor original website design
&lt;/h2&gt;

&lt;p&gt;Lastly, there's no better way to part ways but to share little trivia about the original Meteor website design.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://archive.ph/2p0Ot" rel="noopener noreferrer"&gt;Who designed Meteor's original website?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations, now you're a bit closer to understanding the awesomeness of Meteor and its lore. Hopefully&lt;/p&gt;




&lt;p&gt;About me 👋&lt;/p&gt;

&lt;p&gt;I'm a long time Meteor enthusiast and all around fan who likes to spread the word about Meteor and recently I had been accepted into &lt;a href="https://github.com/sponsors/harryadel" rel="noopener noreferrer"&gt;Github sponsors&lt;/a&gt; program which I'd appreciate if you can donate to, thanks! &lt;/p&gt;

</description>
      <category>meteor</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Meteor And DDP</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Wed, 08 Apr 2020 01:31:26 +0000</pubDate>
      <link>https://forem.com/harryadel/meteor-and-ddp-4kkm</link>
      <guid>https://forem.com/harryadel/meteor-and-ddp-4kkm</guid>
      <description>&lt;p&gt;In this article we're going to talk about the core technology powering Meteor and granting us the ability to create reactive real-time applications. &lt;/p&gt;

&lt;p&gt;DDP is an acronym for Distributed Data Protocol. Don't fall for the fancy name. It's simply a protocol that specifies how to communicate data in JSON. DDP is built using &lt;a href="https://github.com/sockjs/sockjs-client"&gt;SockJS&lt;/a&gt; which is powerful library offering &lt;a href="https://en.wikipedia.org/wiki/WebSocket"&gt;WebSocket&lt;/a&gt; emulation, so it'd allow for WebSocket communication in environments that doesn't support WebSockets. Also, It abstracts away all the low level stuff that goes into making WebSockets communication possible. In short, DDP is nothing but a customized version of SockJS server/client setup.&lt;/p&gt;

&lt;p&gt;Learning about this was such a revelation for me because I finally came to understand why Meteor is nothing but a set of great tools carefully integrated together by some very smart people.&lt;/p&gt;

&lt;p&gt;DDP supports two basic operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote Procedure Calls (RPC).&lt;/li&gt;
&lt;li&gt;Subscribing to set a documents where the server continuously keeps the client updated about changes to these documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's RPC? It's another fancy name for invoking server side operations by the client.    &lt;/p&gt;

&lt;p&gt;You can already tell how those two very basic operations can power &lt;a href="https://guide.meteor.com/methods.html"&gt;Methods&lt;/a&gt; and &lt;a href="https://guide.meteor.com/data-loading.html"&gt;Pub/Sub&lt;/a&gt; pattern of Meteor. It's something that we shall go in great detail in the future. For now, let's keep things simple.&lt;/p&gt;

&lt;p&gt;Now, fire up a simple Meteor application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://install.meteor.com/ | sh

meteor create simple-app
cd simple-app
meteor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open up the browser, navigate to localhost:3000. Bring up the networks tab in the web console, select &lt;code&gt;WS&lt;/code&gt; and click on websocket then reload the page. You'd end up with something similar to the following picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mjI-zYU6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/LN4U0cz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mjI-zYU6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/LN4U0cz.png" alt="simple-app" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see there's a lot going on here, so I'd like to focus only on few messages here. &lt;/p&gt;

&lt;p&gt;The first one being the "connect" message as the client attempts to establish a connection specifying the DDP version used then the server replies with "connected" and sets a unique "session". This "session" is used to differentiate clients accessing the server, so that later on Meteor can tell which message needs to sent out to what clients in later interactions. &lt;/p&gt;

&lt;p&gt;Another delightful thing that you might notice if you leave the browser running for a while is the "ping/pong" messages. This is a heartbeat check to ensure that the connection between the server and client is maintained because if it's not the client'd try to reconnect.&lt;/p&gt;

&lt;p&gt;As you can tell, there's a lot more to DDP than this, we've only scratched the surface. You can learn more about the specification &lt;a href="https://github.com/meteor/meteor/blob/devel/packages/ddp/DDP.md"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For now, keep toying with your Meteor application and witness what funny messages pop in your console for now. In the next few articles we shall go in great detail about what each message mean and how we can build better Meteor applications. &lt;/p&gt;

</description>
      <category>meteor</category>
      <category>node</category>
    </item>
    <item>
      <title>Meteor 1.9 ❤️ Node 12</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Thu, 09 Jan 2020 13:55:54 +0000</pubDate>
      <link>https://forem.com/harryadel/meteor-1-9-node-12-26kj</link>
      <guid>https://forem.com/harryadel/meteor-1-9-node-12-26kj</guid>
      <description>&lt;p&gt;Meteor 1.9 is finally &lt;a href="https://github.com/meteor/meteor/blob/release/METEOR@1.9/History.md"&gt;out&lt;/a&gt;! Supporting Node 12!  &lt;/p&gt;

&lt;p&gt;There're even more awesome changes which are about to hit soon like updating to Cordova 9 and MongoDB to 4.2. What a way to start 2020!   &lt;/p&gt;

&lt;p&gt;And what's even more exciting is that Meteor Meetups are slowly coming back. You can view &lt;a href="https://www.youtube.com/watch?v=utVONxUrres"&gt;Meteor Meetup NYC&lt;/a&gt; where they talk about the new roadmap and next steps.&lt;/p&gt;

&lt;p&gt;You can join the forums to discuss the release &lt;a href="https://forums.meteor.com/t/meteor-1-9-is-out/51358"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy new year guys! &lt;/p&gt;

</description>
      <category>meteor</category>
    </item>
    <item>
      <title>Meteor Galaxy Roadmap</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Thu, 19 Dec 2019 17:00:47 +0000</pubDate>
      <link>https://forem.com/harryadel/meteor-galaxy-roadmap-22hf</link>
      <guid>https://forem.com/harryadel/meteor-galaxy-roadmap-22hf</guid>
      <description>&lt;p&gt;Another great news hits the Meteor community: Meteor Galaxy, the official platform for hosting Meteor application, gets a news a &lt;a href="https://forums.meteor.com/t/meteor-hosting-news-galaxy-roadmap/51181"&gt;roadmap&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The new plan features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Autoscaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native app build and publish&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy from Git push&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New Pricing Plan&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They took into consideration many of the suggestions provided on the &lt;a href="https://forums.meteor.com/t/help-tiny-what-would-it-take-for-you-to-use-galaxy-very-important/50574"&gt;Meteor forums&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With a plan for &lt;a href="https://forums.meteor.com/t/the-new-meteor-roadmap-is-out/51086/30"&gt;Meteor&lt;/a&gt; and &lt;a href="https://forums.meteor.com/t/meteor-hosting-news-galaxy-roadmap/51181"&gt;Galaxy&lt;/a&gt; one can only anticipate the awesome future that awaits us! &lt;/p&gt;

</description>
      <category>meteor</category>
      <category>node</category>
      <category>hosting</category>
    </item>
    <item>
      <title>Meteor Roadmap Announcement</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Wed, 11 Dec 2019 13:35:18 +0000</pubDate>
      <link>https://forem.com/harryadel/meteor-roadmap-3dmh</link>
      <guid>https://forem.com/harryadel/meteor-roadmap-3dmh</guid>
      <description>&lt;p&gt;A new &lt;a href="https://github.com/meteor/meteor/blob/devel/Roadmap.md"&gt;roadmap&lt;/a&gt; has been released detailing the next steps for Meteor, catering to many of the community requests and most importantly encouraging everyone to participate.&lt;/p&gt;

&lt;p&gt;The most notable features of this roadmap are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Update to Node.js 12&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ultra-thin Meteor (Mongo-less Meteor)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hot Module Replacement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update Cordova to 9&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation updates&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This announcement sets out a clear example on how the new Meteor management intends to correct past mistakes, mainly, the lack of communication. &lt;/p&gt;

&lt;p&gt;Again, if you've a couple of minutes to spare, please take a look at the new &lt;a href="https://github.com/meteor/meteor/blob/devel/Roadmap.md"&gt;roadmap&lt;/a&gt;.    &lt;/p&gt;

</description>
      <category>meteor</category>
      <category>node</category>
    </item>
    <item>
      <title>Meteor 1.8.2 is out</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Sat, 30 Nov 2019 09:44:00 +0000</pubDate>
      <link>https://forem.com/harryadel/meteor-1-8-2-is-out-43j9</link>
      <guid>https://forem.com/harryadel/meteor-1-8-2-is-out-43j9</guid>
      <description>&lt;p&gt;As the title suggest a new minor version of &lt;a href="https://www.meteor.com/"&gt;Meteor&lt;/a&gt; is out. The biggest update I'd like to highlight is the inclusion of typescript, which you can add to existing projects by running &lt;code&gt;meteor add typescript&lt;/code&gt; or creating an new one with &lt;code&gt;meteor create --typescript new-typescript-app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/FilipeNevola"&gt;Filipe Névola&lt;/a&gt;, the new Meteor evangelist, wrote a beautiful &lt;a href="https://blog.meteor.com/announcing-meteor-1-8-2-13eab70a4bec"&gt;article&lt;/a&gt; on medium detailing the changes that I highly recommend. &lt;/p&gt;

&lt;p&gt;I hope you have a nice day, bye!    &lt;/p&gt;

</description>
      <category>meteor</category>
      <category>node</category>
    </item>
    <item>
      <title>Tiny acquires Meteor</title>
      <dc:creator>Harry Adel</dc:creator>
      <pubDate>Wed, 09 Oct 2019 10:59:06 +0000</pubDate>
      <link>https://forem.com/harryadel/tiny-acquires-meteor-ddp</link>
      <guid>https://forem.com/harryadel/tiny-acquires-meteor-ddp</guid>
      <description>&lt;p&gt;Yes, you read it correctly! &lt;a href="https://www.tinycapital.com/"&gt;Tiny&lt;/a&gt; just acquired &lt;a href="https://www.meteor.com/"&gt;Meteor&lt;/a&gt; couple of days ago.&lt;/p&gt;

&lt;p&gt;For those of you who don't know it yet &lt;a href="https://www.meteor.com/"&gt;Meteor.js&lt;/a&gt; is a framework that has been around for almost 8 years and there has been lots of ups and downs. Below I'll try to get you up to speed about Meteor and what to anticipate in the near future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meteor primer
&lt;/h2&gt;

&lt;p&gt;Meteor.js was first &lt;a href="https://news.ycombinator.com/item?id=3824908"&gt;unveiled publicly&lt;/a&gt; in April 2012. In a time where most of the technologies we use today either weren't around or wasn't considered as robust, Meteor provided a streamlined experience building realtime applications in a matter of minutes with little to no configuration. It had its own template engine (Blaze), packaging system and a bundling system allowing you to ship to Android/IOS using Cordova.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recent history
&lt;/h2&gt;

&lt;p&gt;Sadly, things hasn't been going as strong for Meteor lately. Lots of new technologies started popping around. In essence Meteor has been a victim of its own success. More importantly, it didn't help out that Meteor Development Group, the company behind Meteor, decided to sidestep Meteor in favor of GraphQL and put it on life support. &lt;/p&gt;

&lt;p&gt;Meteor had only one contributor, &lt;a href="https://github.com/benjamn"&gt;Ben Newman&lt;/a&gt;, for three years! And I must assert that &lt;strong&gt;he did a great job&lt;/strong&gt;. [If one guy kept Meteor going, imagine what a team would do!]      &lt;/p&gt;

&lt;p&gt;The community wasn't thrilled about the current affair of the situation and raised this matter in a recent &lt;a href="https://github.com/meteor/meteor/issues/10477"&gt;issue&lt;/a&gt; which has been culmination of increasing discontent at the situation. The morale had seen better days, though many both long-time Meteor developers and new-comers still advocate for it as an incredible prototyping tool that can get your business up in no time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the future holds
&lt;/h2&gt;

&lt;p&gt;Honestly, I'm hyper optimistic about the future of Meteor. And I'll try to make my case as clear as possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Meteor gets to have a life of its own, with a company supporting it and a dedicated dev Team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Many amazing developers of the community decided to band together and create an &lt;a href="https://github.com/Meteor-Community-Packages/organization"&gt;organization&lt;/a&gt; maintaining vital community Meteor packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Typescript has been added and the core is getting &lt;a href="https://github.com/meteor/meteor/pulls?utf8=%E2%9C%93&amp;amp;q=label%3AProject%3ATypeScript-conversion+sort%3Aupdated-desc+"&gt;converted&lt;/a&gt; to it one module at a time. In general, the code is being made more accessible to have more contributors on board.   &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Release &lt;a href="https://github.com/meteor/meteor/pull/10527"&gt;1.9&lt;/a&gt; is in the works to be out and it shall rock Node 12! How exciting it that?!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I humbly encourage all of you to give Meteor a &lt;a href="https://www.meteor.com/tutorials/react/creating-an-app"&gt;try&lt;/a&gt;, participate in the &lt;a href="https://forums.meteor.com/t/some-exciting-meteor-news/50313/118"&gt;community discussion&lt;/a&gt; around the acquisition, &lt;a href="https://forums.meteor.com/t/help-tiny-post-your-most-wanted-features-for-meteor/50326/94"&gt;suggest&lt;/a&gt; new ways to improve Meteor, and maybe say hi in the &lt;a href="https://bit.ly/2lZncTV"&gt;community organization slack channel&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Big shout out to &lt;a href="https://www.linkedin.com/in/david-panart-b0b07282/"&gt;David Panart&lt;/a&gt; and &lt;a href="https://github.com/StorytellerCZ"&gt;Jan Dvorak&lt;/a&gt; for proof-reading and providing suggestion.&lt;/p&gt;

</description>
      <category>meteor</category>
    </item>
  </channel>
</rss>
