Skip to content

Instantly share code, notes, and snippets.

@Yorickov
Forked from davideluque/!README.MD
Created June 15, 2023 13:09
Show Gist options
  • Save Yorickov/4574aa4fa87a651909d70c82a6e0dc3c to your computer and use it in GitHub Desktop.
Save Yorickov/4574aa4fa87a651909d70c82a6e0dc3c to your computer and use it in GitHub Desktop.

Revisions

  1. @davideluque davideluque revised this gist May 18, 2023. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions sessions_controller.rb
    Original file line number Diff line number Diff line change
    @@ -35,13 +35,13 @@ def apple_callback

    @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
    unless @user.present
    @user = User.register_user_from_apple(id_token.sub, id_token.email)
    return created
    else
    # to prevent an account takeover vulnerability, show the user a message and make them sign in
    # to link their account to apple. the user has to prove the ownership of the account by signin in
    return unprocessable_entity
    end
    end

  2. @davideluque davideluque revised this gist Aug 18, 2020. 1 changed file with 16 additions and 13 deletions.
    29 changes: 16 additions & 13 deletions !README.MD
    Original file line number Diff line number Diff line change
    @@ -1,23 +1,26 @@
    Sign in with Apple in Ruby on Rails using apple_id gem.
    Implementation of the Sign in with Apple service in Ruby on Rails. This implementation is convenient for Ruby on Rails APIs as it does not use views.

    # About this implementation:
    # This implementation does:

    * This implementation is suitable for Rails APIs.
    * Verifies the user associated identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the user's apple account to the user's main account.
    * Verify the user's identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the user's apple account to the user's existing account.

    # Parameters

    * Change this implementation to use safe params.
    * code: Apple's authorizationCode after sign in. Example: c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...
    * id_token: Apple's identityToken after sign in. Example: eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...

    # Error response from Apple

    `params[:code] # apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)`
    The following block:

    begin
    token_response = @client.access_token!
    rescue AppleID::Client::Error => e
    # variable "e" contains the error message from apple.
    return unauthorized
    end

    `params[:id_token] # apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)`
    Rescues from an [ErrorResponse](https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse) received from Apple, due to an invalid value in the *code* parameter.

    # 401 status code

    Cause: the parameter *:code* is invalid and apple may have responded with an [ErrorResponse](https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse).

    This error can occur when the :code parameter is invalid, due to the change of the [Sign in with Apple’s configurations](https://help.apple.com/developer-account/?lang=en#/devde676e696) (identifier, private key, team, key id, redirect URI, etc) or a mismatch between the backend's configuration that makes the request to apple servers (this implementation) and the configuration used in the frontend to show the sign-in page.
    This error can occur when the :code parameter is invalid, because of a change in the [Sign in with Apple’s configurations](https://help.apple.com/developer-account/?lang=en#/devde676e696) (identifier, private key, team, key id, redirect URI, etc) or a mismatch between the backend's configuration that makes the request to apple servers (this implementation) and the configuration used in the frontend to show the Sign in page.
  3. @davideluque davideluque renamed this gist Aug 18, 2020. 1 changed file with 11 additions and 5 deletions.
    16 changes: 11 additions & 5 deletions README.MD → !README.MD
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,20 @@
    Sign in with Apple in Ruby on Rails using apple_id gem.

    # About this implementation:

    * This implementation is suitable for Rails APIs.
    * Verifies the user associated identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the users' apple account to users' main account.
    * Log in the user, register the user or connect the user's apple account to the user's main account.

    # Parameters

    * Change this implementation to use safe params.


    `params[:code] # apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)`

    # params

    * change the implementation to safe params if possible!
    * params[:code] == apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)
    * params[:id_token] == apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)
    `params[:id_token] # apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)`

    # 401 status code

  4. @davideluque davideluque renamed this gist Aug 18, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  5. @davideluque davideluque revised this gist Aug 18, 2020. No changes.
  6. @davideluque davideluque revised this gist Aug 18, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Gemfile.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    gem 'apple_id'
  7. @davideluque davideluque revised this gist Aug 18, 2020. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion Gemfile
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    gem 'apple_id'
  8. @davideluque davideluque revised this gist May 25, 2020. 4 changed files with 17 additions and 8 deletions.
    10 changes: 6 additions & 4 deletions README.MD
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,17 @@
    This implementation is suitable for Rails APIs.

    This implementation:
    # About this implementation:

    * This implementation is suitable for Rails APIs.
    * Verifies the user associated identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the users' apple account to users' main account.

    # params

    * change the implementation to safe params if possible!
    * params[:code] == apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)
    * params[:id_token] == apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)

    # 401 status code

    The parameter *:code* is invalid and apple may have responded with an [ErrorResponse](https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse).
    Cause: the parameter *:code* is invalid and apple may have responded with an [ErrorResponse](https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse).

    This error can occur when the :code parameter is invalid, due to the change of the [Sign in with Apple’s configurations](https://help.apple.com/developer-account/?lang=en#/devde676e696) (identifier, private key, team, key id, redirect URI, etc) or a mismatch between the backend's configuration that makes the request to apple servers (this implementation) and the configuration used in the frontend to show the sign-in page.
    8 changes: 8 additions & 0 deletions application.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    # Enviroment is managed with gem 'figaro'
    # THESE ARE NOT THE REAL ONES, USE YOUR VALUES.

    APPLE_CLIENT_ID: "com.myapp.client"
    APPLE_TEAM_ID: "DX4RM9AL52"
    APPLE_KEY: "51KDRS24J5"
    APPLE_PEM: "-----BEGIN PRIVATE KEY-----\nZj0DAQehRANCAARxcsMPCg29tjBgsO8K8cp3mJIoSu\n+HPFYiW1jNaa+MvTHxMIGTAgEAmBMGByqGSM49AgEgCCqGSM49AwEHBHkwdwIBAQQKj7Hb+b++gCgYIKoZIN\nxPJ3EEpVqz4/rH/ExZSKwaIZ/nCtkvtPUS7Y7IHaBVB94OHNzppD3UE\npYRfzHK+\n-----END PRIVATE KEY-----\n"
    APPLE_REDIRECT_URI: "https://api.myapp.com/auth/apple"
    3 changes: 1 addition & 2 deletions sessions_controller.rb
    Original file line number Diff line number Diff line change
    @@ -64,8 +64,7 @@ def setup_apple_client
    end

    def sign_in_and_return
    sign_in(@user, store: true, bypass: false)
    generate_auth_header
    sign_in(@user, store: true, bypass: false) # Devise method

    render status: 200, json: {
    status: "success",
    4 changes: 2 additions & 2 deletions user.rb
    Original file line number Diff line number Diff line change
    @@ -5,8 +5,8 @@ def self.register_user_from_apple(email, uid)
    User.create do |user|
    user.apple_uid = uid
    user.email = email
    user.provider = :apple
    user.uid = email
    user.provider = :apple # devise_token_auth attribute, but you can add it yourself.
    user.uid = email # devise_token_auth attribute
    end
    end
    end
  9. @davideluque davideluque revised this gist May 25, 2020. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions README.MD
    Original file line number Diff line number Diff line change
    @@ -5,8 +5,8 @@ This implementation:
    * Verifies the user associated identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the users' apple account to users' main account.

    params[:code] == apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)
    params[:id_token] == apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)
    * params[:code] == apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)
    * params[:id_token] == apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)

    # 401 status code

  10. @davideluque davideluque revised this gist May 25, 2020. 2 changed files with 16 additions and 2 deletions.
    16 changes: 15 additions & 1 deletion README.MD
    Original file line number Diff line number Diff line change
    @@ -1 +1,15 @@
    This implementation is suitable for Rails APIs.
    This implementation is suitable for Rails APIs.

    This implementation:

    * Verifies the user associated identity token with apple servers to confirm that the token is not expired and ensure it has not been tampered with or replayed to the app.
    * Log in the user, register the user or connect the users' apple account to users' main account.

    params[:code] == apple's authorizationCode (c49a75458b1e74b9f8e866f5a93b1689a.0.nrtuy. ...)
    params[:id_token] == apple's identityToken (eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNT ...)

    # 401 status code

    The parameter *:code* is invalid and apple may have responded with an [ErrorResponse](https://developer.apple.com/documentation/signinwithapplerestapi/errorresponse).

    This error can occur when the :code parameter is invalid, due to the change of the [Sign in with Apple’s configurations](https://help.apple.com/developer-account/?lang=en#/devde676e696) (identifier, private key, team, key id, redirect URI, etc) or a mismatch between the backend's configuration that makes the request to apple servers (this implementation) and the configuration used in the frontend to show the sign-in page.
    2 changes: 1 addition & 1 deletion user.rb
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    # User model. It is a devise_token_auth user model.
    class User < ApplicationRecord

    def self.register_user_from_apple(uid, email)
    def self.register_user_from_apple(email, uid)
    User.create do |user|
    user.apple_uid = uid
    user.email = email
  11. @davideluque davideluque created this gist May 25, 2020.
    1 change: 1 addition & 0 deletions Gemfile
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    gem 'apple_id'
    1 change: 1 addition & 0 deletions README.MD
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    This implementation is suitable for Rails APIs.
    3 changes: 3 additions & 0 deletions routes.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    Rails.application.routes.draw do
    post 'auth/apple' => 'sessions#apple_callback'
    end
    87 changes: 87 additions & 0 deletions sessions_controller.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    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)
    generate_auth_header

    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
    12 changes: 12 additions & 0 deletions user.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    # User model. It is a devise_token_auth user model.
    class User < ApplicationRecord

    def self.register_user_from_apple(uid, email)
    User.create do |user|
    user.apple_uid = uid
    user.email = email
    user.provider = :apple
    user.uid = email
    end
    end
    end