Docker on Rails 7
Simple dev bootstrap rails with docker-compose. You’ll be up and running as quickly as 1..2…3!
Allowing users to login with multiple authentication providers brings great benefits but also results in some annoying edge cases. For example, what happens when they login with one provider, logout and then login with another? What happens when they try to login with one having already logged in with another?
Typically authentication systems have a User model which handles most of the authentication logic but having multiple logins forces you to correctly separate the concepts of an Identity and a User. An Identity is a particular authentication method which a user has used to identify themselves with your site whilst a User manages data which is directly related to your site itself.
So to start you will want to create both User and Identity models. We will also add some convenience methods for creating identities and users when the OmniAuth callback is invoked:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :identities
def self.create_with_omniauth(info)
create(name: info['name'])
end
end
# app/models/identity.rb
class Identity < ActiveRecord::Base
belongs_to :user
def self.find_with_omniauth(auth)
find_by_provider_and_uid(auth['provider'], auth['uid'])
end
def self.create_with_omniauth(auth)
create(uid: auth['uid'], provider: auth['provider'])
end
end
So a user can have multiple identities and each identity belongs to a single user.
Next we need to handle logging in and logging out. This is managing session data since a logged in user is simply a person who has some session data confirming that they have been logged in. The OmniAuth callback which a provider will redirect to upon authenticating a user is /auth/:provider/callback so lets setup a route and a controller to handle this. We should also setup some helper methods on our Application Controller for handling the current user:
# config/routes.rb
YourAppName::Application.routes.draw do
match '/auth/:provider/callback', to: 'sessions#create'
match '/logout', to: 'sessions#destroy'
end
#app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def create
# Login the User here
end
def destroy
# Logout the User here
end
end
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
protected
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
def signed_in?
!!current_user
end
helper_method :current_user, :signed_in?
def current_user=(user)
@current_user = user
session[:user_id] = user.nil? ? user : user.id
end
end
Now to login, all a user needs to do is go to /auth/provider and they will get redirected to Sessions Controller create method after authenticating. So there are a number of possibilities when they hit this action:
A user has never used your site before. They have no User model and no Identities either. A user is logged out but they have logged into your site with a provider previously. They are now signing in with the same one again. Just as above but they are now signing in with a different provider. A user is logged in with a provider but they try to login with the same provider again. A user is logged in but they try to login with a different provider. The first two cases are just like a normal sign in process. The final 3 cases occur because we are allowing multiple providers and they can be tricky to handle.
Firstly, we need to grab authentication data given to us by the provider which is stored in request.env[omniauth.auth]. Then we need to check whether we have an identity which matches this data or create a new one.
How we proceed for here depends on whether the user is already logged in. If they aren’t logged in then either they are a brand new user (so we treat their request like a registration) or they already have an account (so we treat this like a login request).
If they are logged in then we treat their request like they are trying to link an identity with their account. Either they are trying to link an identity which they have already linked (in which case we should display an error message telling them that) or it is a brand new identity so we go ahead and link it.
So at this point our skeleton create method looks like this:
def create
auth = request.env['omniauth.auth']
# Find an identity here
@identity = Identity.find_with_omniauth(auth)
if @identity.nil?
# If no identity was found, create a brand new one here
@identity = Identity.create_with_omniauth(auth)
end
if signed_in?
if @identity.user == current_user
# User is signed in so they are trying to link an identity with their
# account. But we found the identity and the user associated with it
# is the current user. So the identity is already associated with
# this user. So let's display an error message.
redirect_to root_url, notice: "Already linked that account!"
else
# The identity is not associated with the current_user so lets
# associate the identity
@identity.user = current_user
@identity.save()
redirect_to root_url, notice: "Successfully linked that account!"
end
else
if @identity.user.present?
# The identity we found had a user associated with it so let's
# just log them in here
self.current_user = @identity.user
redirect_to root_url, notice: "Signed in!"
else
# No user associated with the identity so we need to create a new one
redirect_to new_user_url, notice: "Please finish registering"
end
end
end
So at this point, there are a couple of further considerations. Firstly on the signed in/identity not associated with user branch, there are two reasons why an identity might not be associated with a user. It could be that the identity is brand new, having never been used to sign in before. However, it could be that it has been used and so is already associated with a different user, although not necessarily a different person. Given that this user knew the login credentials for that identity, I think it is probably sufficiently prudent to assume that they are, in fact, the same person who also created the previous user. However, by simply reassigning the user to which the identity is associated with to the current one, you not only leave a user model potentially dangling with no identities to sign in with but also prevent the user from merging their data from their previous account in with this one. Resolving this will be dependent entirely on how much data, and the nature of that data, you have stored for each user but for sufficiently simple applications, you could at this point check to see if the old user has any identities left and, if not, delete that user. If the person using your site is likely to lose any data from this process then you would either need to make this sufficiently clear to them before proceeding or provide them with a way to migrate that data over (or handle it automatically, if possible).
Secondly, on the not signed in/no user model branch, you may need more registration data from your user than can be provided by your authentication providers. At this point, as I have assumed above, you can redirect them to a new user form and redirect them to this point if they try to access any other part of the app without completing it. Then create the user and log them in again when they have. Otherwise, if no further data is necessary or mandatory, you can go ahead and create a blank user model in the create method and log them straight in.
Finally, a few lose ends. Here is the destroy method for logging users out:
def destroy
self.current_user = nil
redirect_to root_url, notice: "Signed out!"
end
You’ll also find that the OmniAuth callback url does not correctly verify the rails authenticity token and so will destroy any session data upon returning, thereby logging your current user out. This will prevent them from associating a new identity with their current account. You can get around this by adding skip_before_filter :verify_authenticity_token, only: :create to your sessions controller but I am unsure of the security implications of this.
You’ll also need some migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users
end
end
class CreateIdentities < ActiveRecord::Migration
def change
create_table :identities do |t|
t.string :uid
t.string :provider
t.references :user
end
add_index :identities, :user_id
end
end
Simple dev bootstrap rails with docker-compose. You’ll be up and running as quickly as 1..2…3!
Bootstrap your own jekyll blog with docker-compose
Allowing users to login with multiple authentication providers brings great benefits but also results in some annoying edge cases. For example, what happens ...
Here is serveral usefull source:
Installing(if not available) nano for mac/ubuntu
source
There is 4 ways to excute ruby method. Two of them can excute private method out of self class.
standard timeout require 'timeout' begin complete_results = Timeout.timeout(1) do sleep(2) end rescue Timeout::Error puts 'Print me something ...
problem as written some RSpec test for my rails 3.2 application and because I was annyoed by the Browser popping up ich tried to change from firefox to capyb...
Sometimes we want to simulate browser behavior. The situation can be test or automation script.
Just keep record of the procedure to deploy heroku install heroku heroku gem(deprecated) or toolbelt sudo aptitude install heroku-toolbelt #or for ubuntu wge...
RVM is a great command-line tool which allows you to easily install, manage, and work with multiple ruby environments from interpreters to sets of gems. Thi...
Sometimes we need to generate a ‘n’ character random string.
Sometimes we would also need to generate x random numbers from n to m
While installing RVM on a mac, one of the major problems we face are due to missing Xcode. We usually get the error below:
Sometimes, we need to rake tasks that inserts a paticular user entered value into multiple databases.
Using Net module is the best and the eaiest ways to post data or make api calls to urls. Here’s an example
###strftime###
###MD5 digests### MD5 is a one-way hashing algorithm for creating digest “signatures” or checksums of strings. MD5 digests are 128 bit (16 byte) signatures. ...
Sometimes there are common sets of fields and id’s that we would like the api to filter before sending the details to the client. It would be great if we can...
Another common problem that rails developers face while developing web applications that connect to multiple external systems and api’s is configuring the th...
Extend vs. Include Modules are used for mixins, ruby’s way of handling muliple inheritance without the complications. There are two ways to mixin, either in...
There has been lots of questions on the comparison operators. So we yanked it for you..
There are two commands heavily used by developers for checking their pocesses in unix.
Setting up the rails mailer for development purposes is much easier. using the action mailer to configure the gmail services, is just a few commands and conf...
In Unix, what is screen, and how do I use it?
nohup is an unix command which prevents “hangups on logout” which also means that if you decide you need to logoff the from the session with the server, the ...
classes store methods
Add the following lines to make the rails console to use pry. make sure that
One of the common questions that we get about people coming from other programing languages is with the way switch cases work in ruby.
Add the following gems to the Gemfile and bundle install
One of the most common problems that we come across is when the programmer tries to setup the Mac OS X - name/hostname for the computer, since it takes up mo...
##Here are the steps to push a rails project to Heroku##
RVM is the Ruby environment manager. Here are some of the commonly used commands.
##Adding deligate method in the model##
##Including RSpec in your code:##
Download and install the latest mvim from
Following are some of the common git aliases, people use. Add them to ~/.bash_profile
SOURCE
When the bundling gems fail in Mac OS X, Download the latest GCC standalone file from the link below:
Learning Regular expressions require regular practice. Here are a few ways to practice:
Add configuration
what’s N+1 problem, how to solve the problem ```ruby clients = Client.limit(10) clients.each do |client| puts client.address.postcode end
just got error /Users/ken/.rvm/gems/ruby-1.9.3-p327/gems/rubypython-0.5.3/lib/rubypython/rubypyproxy.rb:198:in `method_missing’: ClassNotFound: no lexer f...
simple way to rescue and retry several tries = 0 begin # some routine rescue tries += 1 retry if tries <= 3 puts "no dice!" end #or 3.times do b...
capybara-webkit
There are many other Selenium gems out there, but this is the only official, maintained gem. If you’re looking for a slightly higher level API built on the s...
```ruby dynamic_name = “TestEval2”
```ruby class A
First, There are serveral revert situation: Local: # this will detach your HEAD, i.e. leave you with no branch checked out. git checkout 0d1d7fc32 git checko...
A happy number is defined by the following process. Starting with any positive integer, replace the number by the sum of the squares of its digits, and repea...
Starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.
Here is some examples to help understand extend and include ```ruby module M_A def test puts true end end
Some frequently use:
```ruby get the submodule initially git submodule add ssh://bla submodule_dir git submodule init