Google has loads of services, and Firebase is one of my favorites because it provides lots of easy to use services that help me write one app for web and mobile.
Today I’ll do a quick sample of how to use the authentication service from Firebase in your rails application.
Most of it is javascript and a callback, we’ll not need any specific gem or anything like that.
I’ll start creating a new rails 6 application with the usual command “rails new firebaseauthtest”
Then, my second step is to go to the Firebase Console https://console.firebase.google.com/ and create a new project, I’ll also call it firebaseautntest as we can see in the image bellow, we also need to accept firebase terms of use.
After the project is created, we need to add a web application to it by clicking in the web icon and providing a name, I’ll use Rails Sample as the name.
When we create the app, firebase will ask us to add the SDK, that is a simple javascript to be added to the application, since it is just a sample, I’ll add it to the main application template that will look like this:
<!DOCTYPE html> <html> <head> <title>Firebaseauthtest</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= yield %> <!-- The core Firebase JS SDK is always required and must be listed first --> <script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-auth.js"></script> <!-- TODO: Add SDKs for Firebase products that you want to use https://firebase.google.com/docs/web/setup#config-web-app --> <script> // Your web app's Firebase configuration var firebaseConfig = { apiKey: "MYAPIKEY", authDomain: "MYDOMAIN.firebaseapp.com", databaseURL: "https://MYDOMAIN.firebaseio.com", projectId: "MYPROJECTID", storageBucket: "MYDOMAIN.appspot.com", messagingSenderId: "SENDERID", appId: "APPID" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); </script> </body> </html>
Now we need to integrate the Firebase authentication into our application, and that is pretty easy, but first, lets go back to firebase console, in the authentication tab and enable at least one authentication method, right now I just enabled google because it was easy.
The localhost domain is already enabled for testing purposes, but when you publish your application you’ll need to go back to the authentication providers page and add your real domain, but for now it is not needed.
You’ll also need to go to google cloud console and at least upload one icon for your application in the oauth screen section in this URL: https://console.cloud.google.com/apis/credentials
Then we’ll go back to our application and create a model to store the data returned by firebase and a controller for the javascript to send the authenticated user data, o we can mirror it on our database:
$ rails g model user firebase_uid:string photo_url:string phone_number:string email:string display_name:string $ rails db:migrate $ rails g controller sessions $ rails g controller home index
Now we need to adjust the routes:
Rails.application.routes.draw do resource :sessions root to:'home#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end
We’ll also want to update the sessions controller to receive the authenticated user and save it, the code for this example will be pretty simple as you can see bellow:
class SessionsController < ApplicationController def create user = User.find_or_initialize_by(firebase_uid: params[:uid]) user.display_name = params[:displayName] user.photo_url = params[:photoURL] user.phone_number = params[:phoneNumber] user.email = params[:email] user.save session[:user_id] = user.id render json: {success: true, user: user} end def destroy session[:user_id] = nil end end
That controller will use the firebase user ID as key and will update our information every time a user logs in, without any filter, so any user with a valid google account can signup for our application.
Of course we can then show another form, ask for more data, …
But that is not in the scope of this sample (but I can add this to the sample if you ask in the comments)
After the controller and routes are set, we can go back to our application.html.erb and add the authentication code, the first part is a simple login button and login status div:
<div id="auth_result"></div><div id="auth_with_google" style="display:none"><button onClick="javascript:authWithGoogle()">Login com o Google</button></div>
The button is initially hidden and the div initially empty, we’ll change that with our Javascript, lets create now the “authWithGoogle” method that is called by the button, add this to any script block in the page:.
function authWithGoogle(){ var provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider).then(function(result) { // This gives you a Google Access Token. You can use it to access the Google API. var token = result.credential.accessToken; // The signed-in user info. var user = result.user; // ... xhttp = new XMLHttpRequest(); xhttp.open('POST','/sessions',true); xhttp.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhttp.setRequestHeader('X-CSRF-Token',$("meta[name='csrf-token']").content) xhttp.send(JSON.stringify({uid:user.uid,displayName:user.displayName,photoURL:user.photoURL,phoneNumber:user.phoneNumber,email:user.email,googleToken: token})) }).catch(function(error) { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; // The email of the user's account used. var email = error.email; // The firebase.auth.AuthCredential type that was used. var credential = error.credential; // ... console.log(error); });
In that function we are creating a google auth provider and showing the user an authentication popup, then the authentication is done we get the user and the google auth token and send to the sessions controller to update our database and store the user information, we are not storing the google token in the database, but we could do that to access google APIs in the user behalf if we requested the permissions, right now we just asked for permission to know the email and full name of the user.
You can change the requested permissions in the google cloud console.
Another thing to do is check if the user is authenticated or not, and we’ll use the firebase onAuthStateChanged listener to do that, the callback will be called as soon as firebase loads the state from the server, right after the page finishes loading.
To do this, just add the following snippet to any script block in your page:
firebase.auth().onAuthStateChanged(function(user) { console.log(user); if (user) { document.getElementById('auth_result').innerHTML = 'User Authenticated'; document.getElementById('auth_with_google').style.display = 'none'; } else { document.getElementById('auth_result').innerHTML = 'User Not Authenticated'; document.getElementById('auth_with_google').style.display = 'block'; } });
That block will take care of the auth status and showing or not the login button.
And that is all you need, with this your app is already using Firebase for authentication, but firebase has a lot more features that we can explore here later.
I’ll be using it for a small ReactNative app I’ll write, so you’ll probably read here again about Firebase and ReactNative 😀
And of course Ruby on Rails that will be used as the app back-end.
If you have any questions about this sample, please leave a comment, I’ll try to answer it as quick as possible.
This isn’t a particularly secure way of dealing with sessions. You have not established trust in your sessions controller. Any old joker could post to that endpoint with those params and they’d get a user record in your system and be signed in.
I believe the token may be a jwt token which you could validate, but you haven’t shown that code here 👀
You are correct, but the idea of the post was just a sample on how to do it, it is mostly Javascript.
if you want a more secure version, it’d be better to implement it in the backend without all the tokens visible on the client.