Rails | Devise | Internationalization I18n

If you are upgrading your app from uni-lingual to bilingual, you might want to have separate pages for Devise related pages as well. Its simple for simpler implementation of devise like no ‘omniauth’. But if you are including ‘omniauth’ then normal recommended approach will not work.

# Before
devise_for :users, path: '',
  path_names: { sign_up: :signup, sign_in: :login, sign_out: :logout },
  controllers: {
    registrations:      'devise_override/registrations',
    confirmations:      'devise_override/confirmations',
    sessions:           'devise_override/sessions',
    omniauth_callbacks: 'vehicle_owners/omniauth_callbacks'
  }
# After
devise_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'vehicle_owners/omniauth_callbacks'}
scope "(:locale)", locale: /en|fi/ do
  devise_for :users, path: '',
             path_names:   { sign_up: :signup, sign_in: :login, sign_out: :logout },
             controllers:  {
                 registrations:      'devise_override/registrations',
                 confirmations:      'devise_override/confirmations',
                 sessions:           'devise_override/sessions'
             }, skip: :omniauth_callbacks

So, it turns out that you cannot scope the Omniauth related URLs, as it makes no sense. You might also see warnings like

Devise does not support scoping OmniAuth callbacks under a dynamic segment
and you have set "/(:locale)/". You can work around by passing
`skip: :omniauth_callbacks` to the `devise_for` call and extract omniauth
options to another `devise_for` call outside the scope. Here is an example:

 devise_for :users, only: :omniauth_callbacks, controllers: {omniauth_callbacks: 'users/omniauth_callbacks'}

 scope '/(:locale)', locale: /ru|en/ do
   devise_for :users, skip: :omniauth_callbacks
 end

Note: Do not forget to restart the application after any major changes.

Spree : oauth : Import Shipping/Billing Address along with PaymentSource from Doorkeeper app

If you already have billing/shipping address and payment info of the store in the OAuth provider [eg. some app with Doorkeeper]; and you don’t want to let your users go through  the hassle to fill the form all over again, then, you can reuse the data. For this, you need to return the info along with user info from the OAuth provider in the response.

# in /app/models/spree/user_decorator.rb
class << self
  def from_omniauth(auth_data)
    spree_user = get_or_set_user(auth_data)

    # Extract address info from the payload and update
    #  can update only shipping or both address with same data
    set_shipping_address(spree_user, auth_data) if spree_user.ship_address.nil?
    set_billing_address (spree_user, auth_data) if spree_user.bill_address.nil?
    
    # You might also import PaymentSource info from parent app
    #   Ex: User has already submitted payment info [CreditCard info] and
    #   and you have Stripe PaymentSource Id, then you can import the data
    #   along with other info. I recommend you to use HTTPS for this. 
    set_payment_source(spree_user, auth_data) if spree_user.payment_sources.blank?
 
    # Set user's role; say in parent app the user is Admin, then you
    #  might want to set that user as an Admin in child app as well.
    #  See the function definition below
    set_roles(spree_user, auth_data)
    
    # Need to return the user object
    spree_user
  end

  private
  # all the methods assisting 'from_omniauth' will go below here

Continue reading

Rails : Omniauth : Doorkeeper : skip the authorization step

Its simple using doorkeeper to allow some trusted client/apps to allow access without manual acceptance. It helps you to connect two applications seamlessly.

 

# In initializers/doorkeeper.rb
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
# Example:
#   > client
#    => #<Doorkeeper::OAuth::Client:0x00000006d5f5b0
#     @application=
#       #<Doorkeeper::Application:0x00000006d5fb28
#       id: 1,
#       name: "store",
#       uid: "6ccaad72daa4f60f8a26ad1a7432e895ad12f6ecd816150c4a162b253cd7c2bd",
#       secret: "7e1bfb8e5ddc481021d9ebf445c438537c45734ad04dd51fa3cdeb8d2b5b47f2",
#       redirect_uri: "http://localhost:3001/users/auth/myapp/callback",
#       scopes: "",
#       created_at: Sat, 05 Mar 2016 16:24:56 UTC +00:00,
#       updated_at: Sun, 06 Mar 2016 04:12:01 UTC +00:00>>
#
#   > resource_owner
#   => #<User:0x00000006d56b68
#         id: 4,
#         email: "myemail@gmail.com",
#         encrypted_password: "$2a$10$Jz9aj4gc2fZGPSc2tPYwU.
skip_authorization do |resource_owner, client|
  client.name == 'store'
end

Rails : OmniAuth : Doorkeeper : Get access to provider’s api

I assume  you have set up your own OmniAuth provider or have used Providers like Facebook, Twitter, LinkedIn.

If in some condition you need to access the api of the OAuth provider like FB’s Graph API and modifying user data in Linked in or Twitter or even in your own provider, you need to verify your authenticity to the provider.

So make sure you keep the access_token given by the provider safe somewhere in session for future use.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def the_pact
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    auth_data = request.env["omniauth.auth"]
    @user = Spree::User.from_omniauth(auth_data)
    if @user.persisted?
      set_return_to_path_for_admin if @user.is_admin?
      sign_in_and_redirect @user, :event => :authentication # this will throw if @user is not activated
      
      token = auth_data['credentials']['token']
      session['the_pact_access_token'] = token
      
      set_flash_message(:notice, :success, :kind => "ThePact") if is_navigational_format?
    else
      session["devise.the_pact_data"] = auth_data
      redirect_to new_user_registration_url
    end
  end

Then some where in controller you can use the token to use the provider’s APIs to access some external resource.

To have a token object you can simple do

class ThePact::Client < OAuth2::Client
  def initialize
    super(
        ENV['thepact_app_id'],
        ENV['thepact_secret'],
        site: ENV['oauth_provider_url'],
        parse_json: true
    )
  end
end

class ThePact::Token < OAuth2::AccessToken
  # Return a new OAuth2::AccessToken specific to the app
  # and the user with the given token.
  def initialize(token)
    super(
        ThePact::Client.new,
        token
    )
  end
end


access_token = ThePact::Token.new(session['the_pact_access_token'])
access_token.post('some/url', params: {url: 'params'}, body: {file: upload})

Note:

if you pass `params` hash then the data will be sent via URL and visible to network sniffers. If you are sending form data use `body` instead.

 

Sources:

http://stackoverflow.com/a/5698954/3437900