--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.
First of all, create various folders and files to build the environment.
$ mkdir sign-in-with-apple-sample
$ cd sign-in-with-apple-sample
$ 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
$ docker-compose run web rails new . --force --no-deps -d mysql --skip-bundle
$ docker-compose build
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
$ docker-compose up -d
$ docker-compose run web rails db:create
If you access "localhost: 3000" and the usual screen is displayed, it is successful.
Now that we're ready, it's time to implement it.
./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.
$ docker-compose run web rails g devise:install
...
create config/initializers/devise.rb
create config/locales/devise.en.yml
$ 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
$ docker-compose run web rails g migration AddColumnsToUsers uid:string provider:string
$ docker-compose run web rails db:migrate
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".
$ 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
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.
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.
Open the "Identifers" tab and select "Services IDs" from the pull-down menu on the right.
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.
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
In this case at the terminal
$ ngrok http 3000
Just hit and write the generated URL on the previous page.
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 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
Access the URL launched by ngrok and confirm that the screen looks like ↑.
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.
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!
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