This is another piece of code I've extrapolated from a Ruby on Rails project I'm currently working on. The code implmenets social login with a RoR API-based application, targeted at API clients.
The setup does not involve any browser-redirects or sessions as you would have to use working with Omniauth.
Instead, what it does is takes an access_token generated on client-side SDKs, retireves user info from the access token
and creates a new user and Identity
in the database.
This setup works with native applications as described in the Google iOS Sign In Docs (see Authenticating with a backend server) A quote from that page pretty much sums up how this works:
After you have verified the token, check if the user is already in your user database. If so, establish an authenticated session for the user. If the user isn't yet in your user database, create a new user record from the information in the ID token payload, and establish a session for the user. You can prompt the user for any additional profile information you require when you detect a newly created user in your app.
The basic flow of the login is:
- You sign in using the respective native sdks on your mobile/js clients
- An access_token/id_token is retireved from the mobile SDKs
- A request is made to your Rails Application
POST /identities/:provider
with the token - The server then fetches the user data from the token after validating it with the provider
- The server then creates a user profile &
Identity
based on that information.
In comparison, if you were using Omniauth you would have to:
- Open up a web view in your mobile app linking to your backend
- Your user is redirected from your backend to the external provider
- The user signs in with the external provider
- The user is redirected back to your backend
- The backend generates a user account
- Then it generates an auth token (assuming you're using JWT auth)
- The backend redirects the user back to the native app with the access token.
So there's an advantage to using this setup, it's a lot easier to work with a single JSON API endpoint than it is to work with a bunch of redirects in a web view.
However, if you are using a traditional Rails Application (not a rails api), you should totally go with Omniauth and save all the hastle.
The code is quite modular so it will be extemely easy to add support for another login provider if needed. It's a matter of creating another Provider::Base
super class and including the provider name in your Identity.providers
array.
There are quite a lot of files in this, each with different roles.
An abstract class that serves as a base for all the different login providers making it easy to implement new providers if needed. Provides common extractions for methods.
A provider class for handling login with facebook.
Note: The fields requested must match those requested on the native side otherwise you're going to get a run-time error becuase the granted acccess token won't have adequate permissions
A provider class for handling login with google. You need to provide an array of client_ids for the different clients you will have needing to sign in e.g. iOS App, Android App, React App, Windows App etc.
In this example, I only have one (ios_client_id)
Just an object to handle the credentials (access token/refresh token)
This provides a common interface for the User Information retrieved from the external APIs. Similar to the Omniauth env['auth']
So each user has what I call an Identity
which is just an external login. If you want your app to only support one external login e.g. facebook only, this identities table wouldn't be needed as you could just store the uid/provider/access_token
on the user model.
The identities table is structured like so:
create_table :identities do |t|
t.belongs_to :user, null: false, foreign_key: true
t.string :provider, null: false
t.string :uid, null: false
t.string :access_token
t.string :refresh_token
t.jsonb :auth_hash, default: {}
t.timestamps
end
add_index :identities, %i[provider uid], unique: true
Right now, I've implemented only two providers - Google and Facebook auth. The dependencies that I'm using are
gem 'google-id-token', git: 'https://github.com/google/google-id-token.git'
gem 'koala'
Note: Make sure to install the google-id-token gem from the repo (as of 26th April 2020) in order to support passing an array of client_ids
The tests can be found here
This looks quite useful! Thanks for posting. One question: where is the controller before action
authenticate_for_identity!
method implemented?