class SessionsController < ApplicationController before_action :setup_apple_client, only: [:apple_callback] def apple_callback return unprocessable_entity unless params[:code].present? && params[:identity_token].present? @client.authorization_code = params[:code] begin token_response = @client.access_token! rescue AppleID::Client::Error => e # puts e # gives useful messages from apple on failure return unauthorized end id_token_back_channel = token_response.id_token id_token_back_channel.verify!( client: @client access_token: token_response.access_token, ) id_token_front_channel = AppleID::IdToken.decode(params[:identity_token]) id_token_front_channel.verify!( client: @client, code: params[:code], ) id_token = token_response.id_token # You may want to change the "find_by" method to a less time consuming method. # id_token.sub, a.k.a apple_uid is unique per user, no matter how many times you perform the same request. @user = User.find_by(apple_uid: id_token.sub) return sign_in_and_return if @user.present? @user = User.find_by_email(id_token.email) if @user.present # Enable apple login to existing user @user.update_column(:apple_uid, id_token.sub) return sign_in_and_return else @user = User.register_user_from_apple(id_token.sub, id_token.email) return created end end private def created render status: 201, json: { status: "success", data: @user } end def setup_apple_client @client ||= AppleID::Client.new( identifier: ENV['APPLE_CLIENT_ID'], team_id: ENV['APPLE_TEAM_ID'], key_id: ENV['APPLE_KEY'], private_key: OpenSSL::PKey::EC.new(ENV['APPLE_PRIVATE_KEY']), redirect_uri: ENV['APPLE_REDIRECT_URI'] ) end def sign_in_and_return sign_in(@user, store: true, bypass: false) # Devise method render status: 200, json: { status: "success", data: @user } end def unauthorized render status: 401, json: { status: "error" } end def unprocessable_entity render status: 422, json: { status: "error" } end end