How to implement a simple API for ‘Sign in with Apple’

Image for post
Image for post

Most of the projects I’m working on right now are related to mobile applications and Ruby on Rails backend. Today we will talk about what needs to be done on your server to support mobile ‘Sign in with Apple’ feature. Our goal is to create a simple API that will allow registration and login process to our system using Apple account and mobile application.

The first thing we need to do is to read the documentation to know if there will be any communication between our system and Apple servers and how to implement it. After a quick read, we will know two things:

  • We need to fetch Apple’s public key for verifying token signature.
  • We need to verify Identity Token which will be sent to us from the mobile application together with nonce. The verification process is described here and we can do it by ourselves or use AppleId gem.

Let’s start by implementing a simple AppleService which will be responsible for all the things described above. We will use the AppleId gem because there is no reason to implement that logic from scratch.

Service has one public method fetch_profile where identity token is verified and decoded. The whole process is handled by AppleId gem. Here are a few things worth mentioning:

  • There is no full nameinside the identity token which was a big surprise for me. Unfortunately, we will need to get this value from the mobile application.
  • The nonce is not required but it will increase overall security so it is worth adding.
  • Apple allows to flag user email address as private and in such cases, the email attribute will contain Apple generated alias which later can be used to send messages to the user. There is a separate attribute to indicate if the email address is private or not.
  • ClientId is an identifier of mobile application and you need to ask a mobile developer for it.

Now we can take care of the necessary things around it. We will use concepts from Domain-Driven Design to make things a little more interesting.

Bounded context

This context will be responsible for managing user accounts. We have many possible names: Access, Users, Identity&Access. Bounded contexts are represented as modules in Ruby on Rails application.

Command

We need one command responsible for registration. We will skip validations to keep things clean but feel free to includeActiveModelor dry-struct.

Domain event

We also need one domain event to represent the fact of user registration. It’s good practice to validate event schema. Here we use ClassyHash gem but dry-struct will work too. In some cases, it’s also worth introducing failure events but we don’t need it here.

Aggregate

Depends on the approach we can treat registration as part of Useraggregate or we can create a separate aggregate for the registration process only and create User aggregate later. We will handle all event-related things with RES gem.

There is a simple validation if the user is already registered and if everything is fine thenUserRegisteredFromAppledomain event is published.

Command handler

Now we need to load our aggregate and handle the registration command. This part is done by a command handler. Here you can also validate command and run different policies required to check if a command can be performed. We can make sure that users will be unique thanks to using an email address as part of the stream name.

Communication between commands and command handlers can be implemented with a command bus.

Read model

Our domain model is event-sourced so we need a read model to represent a cached list of registered users. It’s not very efficient to load all aggregates in the system and check who is registered. We also shouldn’t keep view related data inside aggregate. To make this work, the event store should be configured first.

Application service

We introduce an application service to keep controller action as clean as possible. Service is responsible for checking if there is a need to create a new user account or only generate a session for an existing user. Implementation of GenerateSession service is not part of this blogpost.

The only thing left is the controller and routes. It’s pretty straight forward and nobody should have problems with it.

What’s more to do?

Of course, we need to implement a mobile application part, but that topic is for another time. If we want to send emails to generated Apple aliases then we need to use Private Email Relay Service and we need to properly configure our domain in Apple Developer Center.

More DDD content

If you want to see how to implement other DDD patterns in Ruby on Rails you can check out my sample applications and bounded contexts here.

Written by

I’m a Ruby on Rails developer :)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store