[Apple login] Sign in with Apple implementation procedure (Ruby on Rails)

Target audience

--People who want to implement Apple login (Sign in wth Apple) in web applications created with Ruby on Rails

Apple login has come to be seen frequently in various services recently, but I felt that there was surprisingly little information in Japanese regarding the implementation procedure, so I would like to leave it as a memo.

Since we start from the point where we start writing the Dockerfile, we should be able to reproduce it in the same way if we follow the procedure properly.

Preparation

First of all, create various folders and files to build the environment.

Create directory

$ mkdir sign-in-with-apple-sample
$ cd sign-in-with-apple-sample

Create various files

$ touch Dockerfile
$ touch docker-compose.yml
$ touch Gemfile
$ touch Gemfile.lock

./Dockerfile


FROM ruby:2.6.6

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

ENV APP_PATH /myapp

RUN mkdir $APP_PATH
WORKDIR $APP_PATH

COPY Gemfile $APP_PATH/Gemfile
COPY Gemfile.lock $APP_PATH/Gemfile.lock
RUN bundle install

COPY . $APP_PATH
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: root
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

  web:
    tty: true
    stdin_open: true
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
      - ./vendor/bundle:/myapp/vendor/bundle
    ports:
      - "3000:3000"
    links:
      - db

volumes:
  mysql-data:

./Gemfile


source 'https://rubygems.org'
gem 'rails', '~> 5.2.4'

rb:./Gemfile.lock


#OK in the sky

Create a Rails project

$ docker-compose run web rails new . --force --no-deps -d mysql --skip-bundle
$ docker-compose build

Rewrite database.yml

yml:./config/database.yml


default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password #It should be blank by default, so add it
  host: db #The default is "localhost", so rewrite

development:
  <<: *default
  database: myapp_development

Start container & create database

$ docker-compose up -d
$ docker-compose run web rails db:create

Access localhost: 3000

スクリーンショット 2020-12-25 12.08.56.png

If you access "localhost: 3000" and the usual screen is displayed, it is successful.

Implementation

Now that we're ready, it's time to implement it.

Preparation of various gems

./Gemfile


...

gem 'devise'
gem 'omniauth'
gem 'omniauth-apple'

Install the various gems used this time.

$ docker-compose build

I updated the Gemfile, so build it again.

Install Devise

$ docker-compose run web rails g devise:install

...

create  config/initializers/devise.rb
create  config/locales/devise.en.yml

Create User model

$ docker-compose run web rails g devise user

...

invoke  active_record
create  db/migrate/20201224203750_devise_create_users.rb
create  app/models/user.rb
invoke  test_unit
create  test/models/user_test.rb
create  test/fixtures/users.yml
insert  app/models/user.rb
route   devise_for :users

Added column for OmniAuth

$ docker-compose run web rails g migration AddColumnsToUsers uid:string provider:string
$ docker-compose run web rails db:migrate

Added modules and methods for OmniAuth

app/models/user.rb


class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:apple] #← Add

  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.provider = auth.provider
      user.uid = auth.uid
      user.email = auth.info.email
      user.password = Devise.friendly_token[0, 20]
    end
  end
end

In the default state, there are no "omniauthable" and "omniauth_providers" modules, so add them and specify "apple".

Create various controllers

$ docker-compose run web rails g devise:controllers users

...

create  app/controllers/users/confirmations_controller.rb
create  app/controllers/users/passwords_controller.rb
create  app/controllers/users/registrations_controller.rb
create  app/controllers/users/sessions_controller.rb
create  app/controllers/users/unlocks_controller.rb
create  app/controllers/users/omniauth_callbacks_controller.rb

Various controllers are created at once, but this time all you need is "omniauth_callbacks_controller.rb".

rb:./app/controllers/users/omniauth_callbacks_controller.rb


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  skip_before_action :verify_authenticity_token, only: %i[apple]

  def apple
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication
      set_flash_message(:notice, :success, kind: 'Apple') if is_navigational_format?
    else
      session['devise.apple_data'] = request.env['omniauth.auth'].except('extra')
      redirect_to new_user_registration_url
    end
  end
end

Create Rack middleware

Reference article: Rails and Rack

rb:./config/initializers/omniauth.rb


Rails.application.config.middleware.use OmniAuth::Builder do
  provider :apple, ENV['CLIENT_ID'], '',
           {
             scope: 'email name',
             team_id: ENV['TEAM_ID'],
             key_id: ENV['KEY_ID'],
             pem: ENV['APPLE_PRIATE_KEY'],
           }
end

Create a file called "omniauth.rb" under config/initializers /.

To implement Sign in with Apple, register with Apple Developer

You need to get four values ​​such as.

Reference article: How to register an Apple Developer account How to create a certificate required to implement Sign in with Apple

There are already various articles in the world about this area, so please get your own.

Return URLs settings

From here, it will be the operation on the Apple Developer side for a moment.

Finally, specify the redirect destination after authentication is allowed.

Jump to "Certifications, Identifers & Profiles" from the Apple Developer dashboard.

80911786-8d52f100-8d73-11ea-8bbd-39af246f5aee.png

Open the "Identifers" tab and select "Services IDs" from the pull-down menu on the right.

80911889-25e97100-8d74-11ea-8220-b0fe6ba46875.png

Clicking "configure" to the right of "Sign in with Apple" will bring up a screen for registering "Domains and Subdomains" and "Return URLs", so describe each one.

スクリーンショット 2020-12-25 13.02.54_censored.jpg

Domains and Sub domains → 「http://」「.The part excluding "com" etc.
Return URLs → "users" behind/auth/apple/"Callback" added

One caveat is that you can't use "localhost" with Sign in with Apple.

Therefore,

localhost:3000
http://localhost:3000/users/auth/apple/callback

Even if you write such as, it will be played as an invalid value.

Therefore, I will try this time by having a local server assign an appropriate domain, such as "ngrok".

Reference article: ngrok is too convenient

80912200-4e726a80-8d76-11ea-9ca3-a87b3deb3854.png

In this case at the terminal

$ ngrok http 3000

Just hit and write the generated URL on the previous page.

Create a view

I'm pretty much ready, but I haven't created a view yet, so I'll create one.

$ docker-compose run web rails g controller home index

app/views/home/index.erb


<h1>Sign in with Apple</h1>

<% if user_signed_in? %>
  <%= link_to 'logout', destroy_user_session_path, method: :delete %>
  <h1>Hello、<%=  current_user.email %>!</h1>
<% else %>
  <%= link_to 'login', user_apple_omniauth_authorize_path %>
<% end %>

Set up routing

Set the routing so that the functions implemented so far work normally.

rb:./config/routes.rb


Rails.application.routes.draw do
  root to: 'home#index'
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
end

Operation check

スクリーンショット 2020-12-25 11.31.35.png

Access the URL launched by ngrok and confirm that the screen looks like ↑.

80911472-8dea8800-8d71-11ea-8b4d-0fd215100e15_censored.jpg

When you press the "login" button, you will be taken to the Sign in with Apple page, so enter your Apple ID and password respectively.

スクリーンショット 2020-12-25 11.42.46_censored.jpg

If the authentication is successful, the screen will switch to the screen displaying your email address. ("Privaterelay.appleid.com" is a unique domain granted for Sign in with Apple)

This completes the implementation of Sign in with Apple. Thank you for your hard work!

Afterword

I got the impression that registering with Apple Developer and obtaining various certificates would be troublesome, but I feel that the code implementation itself was unexpectedly easy.

The UI may change, but I think the basic idea itself will not change, so I would appreciate it if you could refer to it.

Recommended Posts

[Apple login] Sign in with Apple implementation procedure (Ruby on Rails)
Implementation of Ruby on Rails login function (Session)
[Rails] Procedure for linking databases with Ruby On Rails
Login function implementation with rails
Ruby on Rails <2021> Implementation of simple login function (form_with)
[rails] Login screen implementation in devise
Ruby on Rails Japanese-English support i18n
[Ruby on Rails] Implement login function by add_token_to_users with API
[Ruby on Rails] Comment function implementation
[Ruby on Rails] View test with RSpec
Ruby on Rails in Visual Studio Codespaces
[Ruby on Rails] Follow function implementation: Bidirectional
[Ruby on Rails] Controller test with RSpec
(2021) Ruby on Rails administrator (admin) login creation
[Ruby on Rails] Model test with RSpec
[Ruby on rails] Implementation of like function
Beginners create portfolio in Ruby on Rails
Validation settings for Ruby on Rails login function
[Beginner Procedure Manual 2] Ruby on Rails: Rails template creation
Introducing Rspec with Ruby on Rails x Docker
[Rails 6.0] "Easy login" implementation procedure required for portfolio
Recommendation of Service class in Ruby on Rails
Publish the app made with ruby on rails
Rails new in Ruby on Rails ~ Memorandum until deployment 2
Ruby on Rails Email automatic sending function implementation
Rails new in Ruby on Rails ~ Memorandum until deployment 1
(Ruby on Rails6) Creating data in a table
Determine the current page with Ruby on Rails
[Ruby on Rails] How to install Bootstrap in Rails
Ruby on Rails address automatic input implementation method
[Ruby on Rails] Upload multiple images with refile
I made a portfolio with Ruby On Rails
[rails] gem'payjp' implementation procedure
Ruby on Rails Elementary
Ruby on Rails basics
Ruby On Rails Association
[Ruby on Rails Tutorial] Error in the test in Chapter 3
[Ruby on Rails] Delete s3 images with Active Strage
Run Ruby on Rails RSpec tests with GitHub Actions
[Ruby on Rails] Implementation of tagging function/tag filtering function
[Ruby On Rails] How to reset DB in Heroku
[Ruby on Rails] Post image preview function in refile
Solve the N + 1 problem with Ruby on Rails: acts-as-taggable-on
Created RSS / Atom format sitemap with Ruby on Rails
Explanation of Ruby on rails for beginners ⑦ ~ Flash implementation ~
Ruby on rails learning record -2020.10.03
Difficulties in building a Ruby on Rails environment (Windows 10) (SQLite3)
Definitely useful! Debug code for development in Ruby on Rails
Portfolio creation Ruby on Rails
[Ruby on Rails] Debug (binding.pry)
Ruby on rails learning record -2020.10.05
Ruby on rails learning record -2020.10.09
Ruby on Rails config configuration
[Ruby on Rails] Add a column with a foreign key constraint
[Ruby on Rails] Quickly display the page title in the browser
Japaneseize using i18n with Rails
Ruby on Rails basic learning ①
[Ruby on Rails] about has_secure_password
Ruby on rails learning record-2020.10.07 ②
How to display a graph in Ruby on Rails (LazyHighChart)
Commentary on partial! --Ruby on Rails