Earlier this year PayPal launched the new Log In with PayPal experience worldwide. Integrating it into Rails is very straightforward with the help of Omniauth so I thought I'd show how with a little tutorial.
Log In With PayPal is much like Log In with Twitter or Log In with Facebook but it has the cool benefit of being able to provide you some extra user information like:
All the code for this tutorial is available on Github.
Log In With PayPal uses OAuth 2.0/OpenID Connect 1.0 and the full documentation can be found on the PayPal developer portal. Because it's based on OAuth 2.0 we're going to have to get some OAuth credentials that we can use in our app.
The developer portal doubles as the sandbox and application control panel. Head over and login to get yourself some fresh credentials.
Your app is now created and you can find your credentials under Application Details. Please note that there are 2 sets of credentials. Your live credentials can be used for actual payments and actual logins with real PayPal accounts. Your test credentials are there for using sandbox accounts (read fake/mock accounts) when in development.
We are going to have to enable Log In With PayPal for our app.
http://127.0.0.1:3000/auth/paypal/callback
(or something else if you don't run your Rails app like this)Optionally you can now toggle Accept Payments to off if you don't intend to take payments with this app.
If you want to run Log In With PayPal in sandbox (a.k.a. test) mode you will need to create some fake test accounts.
Unless you have any specific needs I advice you only fill in the following:
Using sandbox account for Log In With PayPal is very useful as it allows you to login as different users. At no point should you be sharing your live PayPal username and password with other developers for testing.
Assuming a plain Rails app adding Log In With PayPal is now pretty straightforward.
Add the omniauth gem to your Gemfile
and bundle.
gem "omniauth-paypal"
Now add the following snippet into config/initializers/omniauth.rb
.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :paypal, "AZj...yjx", "EL4...Q3s", sandbox: true, scope: "openid"
end
Replace the "AZj...yjx"
and "EL4...Q3s"
with own API Client ID and Secret respectively. Also make sure to set the sandbox value to false
when using your Live API credentials.
Now that your app is setup and running visit http://127.0.0.1:3000/auth/paypal
to start the login. Go try it and use that test account you just set up!
You may notice that after the login the app will return back to your app on the /auth/paypal/callback
path and then fail. This is because we still need to implement this functionality.
We need to do 2 things. First we add a catch for this route in config/routes.rb
.
LoginWithPaypalDemo::Application.routes.draw do
...
get "/auth/paypal/callback", to: "sessions#create"
..
end
We then need to implement this action in app/controllers/sessions_controller.rb
much like this:
class SessionsController < ApplicationController
def create
uid = request.env['omniauth.auth'].uid
user = User.where(uid: uid).first_or_create
session[:user_id] = user.id
redirect_to :root
end
end
The magic here is in the request.env['omniauth.auth']
object, which is generated by Omniauth and will look something like this:
{
"provider" => "paypal",
"uid" => "VE..Yk",
"info" => #<OmniAuth::AuthHash::InfoHash>,
"credentials" => #<OmniAuth::AuthHash>,
"extra" => #<OmniAuth::AuthHash>
}
You can use the uid
to identify the user. I used it to find or create a User
record, but you could also tie it to a different model. In my case I can then use the following to find the user:
class SessionsController < ApplicationController
...
def show
@user = User.find_by_id(session[:user_id])
end
...
end
That's it! Most of my code can be seen on Github and it includes a full login and logout. I want to highlight a few extra things though that are available to you that are pretty useful.
Getting the user's UID is fun but rather limiting. Luckily Log In With PayPal allows for a whole lot of extra attributes to be requested. By default you have access to all attributes in the openid
scope.
Let's change our code to also request the user's name and email address. If we look at the table we see that for this we need to add the email
and profile
scopes to our Omniauth configuration.
The first step is to add these scopes to Omniauth:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :paypal, ... scope: "openid email profile"
end
Please make sure not to comma seperate the scopes as this will cause issues later.
Secondly we need to update our application settings.
Now if we try to login again we can access the user's extra details as follows:
class SessionsController < ApplicationController
def create
auth_hash = request.env['omniauth.auth']
uid = auth_hash.uid
name = auth_hash.info.name
name = auth_hash.info.email
user = User.where(uid: uid).first_or_create!(name: name, email: email)
session[:user_id] = user.id
redirect_to :root
end
end
Omniauth can be slow and especially in tests this can be frustrating. Luckily Omniauth has a test mode that allows you to skip the PayPal flow and just "pretend" like a user has logged in.
Add the following to your config/initializes/omniauth.rb
:
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:paypal] = OmniAuth::AuthHash.new({
provider: "paypal",
uid: "1234",
info: {
name: "John Doe",
email: "john@example.com"
}
})
Your app will now skip the PayPal servers and login with the details from above. I use this a lot in my automatic tests.
That's it for now but I will soon be doing a few more of these. I'm planning at least an overview of how to do this in Sinatra and I also want to demo how to use Seamless Checkout on top of this tutorial.