I'm planning to use rails-api for providing JSON API for iOS mobile application to consume. The process:
User open the mobile app
User taps on Facebook connect
Mobile app get fb_access_token and post it to API server to identify the user
API server get user profile on Facebook by using fb_access_token
API server either create and look up the user, then response with a api_token for this particular user
Mobile app use the api_token response for all communication afterward.
Which authentication should be the best option for this app? oAuth2 or BasicAuth? I tried rails-api with doorkeeper, but it doesn't work out of the box because doorkeeper need some assets.
I am doing a basic authentication for this integrated with devise.
First i get the post parameters from the mobile application (the access_token and other stuff).
Then I use open-api to get the user details from facebook:
url = "https://graph.facebook.com/me?access_token="
begin
content = open(URI.encode(url + params[:user][:access_token]))
rescue OpenURI::HTTPError #with this I handle if the access token is not ok
return render :json => {:error => "not_good_access_token" }
end
Now Facebook returns the response
status = content.status[0]
content = ActiveSupport::JSON.decode(content)
if status == "200"
#get the email and check if the user is already in the database. If there is not email, check by the facebook id
#If the user exists return the user. If the user does not exists create new
Hope this helps
Than you can user the same code also for google, just change the url to "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
I'd give omniauth-facebook at try, as it uses OAuth2 and is pretty easy to use. All omniauth strategies are rails middleware, so you just need to add gem 'omniauth-facebook' to your gemfile and add the following to config/initializers/omniauth.rb and you will be able to use /auth/facebook to log in via facebook and /auth/facebook/callback to create the user session (you might want to alter the :display key-value as pop-ups in mobile might not be aesthetically pleasing):
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'],
:scope => 'email,user_birthday,read_stream', :display => 'popup'
end
The facebook auth token will be in the request.env['omniauth.auth'][:credentials][:token] that gets returned to your callback endpoint, which you can integrate into your mobile authentication strategy. see the above link and the omniauth main page for more details.
Related
I am trying to implement AWS Cognito into my application for better all round authentication. The system is a Rails application that is currently using Warden/Devise as the method for handling user accounts (Login,Registration).
My goal is to have a AWS UserPool that contains the list of users for the application. When a user is verified using Cognito I wish to then search the tables that we currently use for the role and move the user to the correct area of the system based on the role that they are assigned too.
I have started to implement the logic to handle this but have come up against a brick wall.
Please see below my code.
cognito_authenticatable.rb
Logic for handling the cognito authentication. All i want to do here is check that the user is registered and return the valid token so i can prefer internal application checks to gather the user role.
def authenticate!
if params[:login]
region_name = 'us-east-2'
user_pool_id = 'us-east-2_Qj78BNQon'
client_id = '1pv3eno72e51mll3q36cuiojmr'
client = Aws::CognitoIdentityProvider::Client.new(
region: region_name
)
resp = client.initiate_auth({
client_id: client_id,
auth_flow: "USER_PASSWORD_AUTH",
auth_parameters: {
"USERNAME" => email,
"PASSWORD" => password
}
})
end
end
divise.rb
This code is just to add the new authentication strategy to the applications warden service.
config.warden do |manager|
manager.strategies.add(:cognito,
Devise::Strategies::CognitoAuthenticatable)
manager.default_strategies(:scope => :login).unshift :cognito
manager.default_strategies(:scope => :login).pop
end
The output error within the console is
Aws::Errors::MissingCredentialsError (unable to sign request without credentials set):
config/initializers/cognito_authenticatable.rb:23:in `authenticate!'
and here is an image from the localhost application that was running.
Any help on this would be amazing.
Thanks in advance.
One solution could be to uncheck the option for generating a client secret when you create the app client in the Cognito user pool. This option is checked by default and you have to know to uncheck it (https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html).
By default, user pools generate a client secret for your app. If you don't want that to happen, clear Generate client secret.
It's only possible to uncheck the client secret during the creation of a new client, so you might have to delete your client app and create a new one (not a big deal).
I also collect my learnings on Cognito, Devise, Rails and VueJS in a Medium article: https://medium.com/#morgler/beta-learnings-from-developing-vuejs-quasar-aws-amplify-and-cognito-application-dd38ec58b881
You are getting this error due to your AWS SDK for Ruby not being configured correctly. That error would likely exist not only for Cognito APIs, but it would exist for any AWS Signature V4 signed API calls. Kindly refer to this documentation to configure your SDK correctly for your application.
So I'm trying to make an app where I want to allow users to login using their Instagram accounts. This is a Rails app. I'm mostly following Railscast 241 for doing this except that I use Instagram API instead of Twitter API. I'm not using devise.
I installed the gem 'omniauth-instagram' and I have the following in one of my initializers -
Rails.application.config.middleware.use OmniAuth::Builder do
provider :developer unless Rails.env.production?
provider :instagram, ENV['MY_CLIENT_ID'], ENV['MY_CLIENT_SECRET']
end
The problem is that when I direct the user to the 'auth/instagram' path the request does not contain my client-id (I check the Chrome debugging tools > Network to make sure of this). And as a result, although it takes the user to the login page, but then it fails and gives the following response -
{"code": 400,
"error_type": "OAuthException",
"error_message": "You must include a valid client_id, response_type, and redirect_uri parameters"}
So instead of making the request o 'auth/instagram' path I direct the user to the actual autorization URL i.e.
https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code
And all goes according to the plan. Except that I don't get the user information as a part of request.env['omniauth.auth'] inside my controller method (after being successfully redirected to the right URL). Infact request.env hash does not have omniauth.auth as one of its keys. The fix to this is that I'll manually have to write a curl -F query to the API to get the user information.
But that sounds like too much work and I feel there must be something that I might have been doing wrong. Why isn't the gem making the correct request with my provided client_id? and why isn't 'omniauth.auth' get properly populated as part of the params?
Some relevant resources -
Instagram API authentication page
omniauth-instagram gem
I'm writing a little app for Coinbase and I'm making an initializer that I've thrown in omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :coinbase, ENV["COINBASE_CLIENT_ID"], ENV["COINBASE_CLIENT_SECRET"], scope: "sell send transfers user"
end
I want to be able to access this anywhere in my app so that I can create an access_token for the user. Based on their docs and the research I've done into Omniauth, I'm not quite sure how to do this.
Should I throw an instance variable and an = in front of the code posted above? Is that a correct solution? Also, how do I go about getting an access_token after initializing this?
Thanks!
Check out this section in the omniauth docs: https://github.com/intridea/omniauth#integrating-omniauth-into-your-application
You first want to set your OAuth redirect URL on Coinbase to /auth/coinbase/callback. Then, when users are redirected back to your site at that path with the code OmniAuth will do its magic and set a special hash called the Authentication Hash on the Rack environment which contains some info about the user as well as the OAuth credentials.
Now I'm looking for more than two days for a solution for the following problem. I have an EmberJS client-side javascript app, that uses my server-side Rail RESTful API. Authorization is done via access token.
Now I want to give the user the ability also to login with OAuth2 from Google, Twitter and so on. Discourse uses OmniAuth for third party login, but OmniAuth needs a server side session. Because I build a RESTful and stateless API, I didn't want to use a session on the server. So I decide to build it on my own with help of Google+ Sign-In for server-side apps, but the example there also uses a session.
Does anyone have a solution for a similar problem or some hints for solving my problem?
EDIT 1
Because OmniAuth doesn't fit well in my setup, I started to create a own implementation for third-party OAuth2 login following Googles help. Everything works fine at the moment. But I didn't implement the CSRF protection explained under heading 1. Create an anti-forgery state token on the site from Google mentioned above. My problem is, how could I store this CSRF token without using a session. Would it be enough to store it in Database and look it up in the callback request from Google later?
EDIT 2
I followed this railscast. There three possible cases, if a user want to sign-in with an extern oauth provider:
The user already signed-up with extern oauth, then he got a Doorkeeper access token.
The user has an account, but didn't sign-in with extern provider before. After oauth flow we only have to create a new authentication for this user.
The user didn't have an account and now tries to sign-in with extern provider. Here we have to redirect the user to the sign-up page, we also can use informations from the oauth provider to pre-fill in the sign-up form, but until the user pushes the sign-up button, we have to save the authentication.
My question now is, what is a good practice to save such informations (authentication, oauth2 csrf-token) server-side in a REST API, without using a session. I have to save these information on the server, because the user should not have the possibility to manipulate them on the client-side.
Maybe I also should create a new question for pros and cons of token and session authentication with Ember apps and possible solutions for both?
Here is my authentication controller:
class Api::V1::AuthenticationsController < ApplicationController
def oauth
# redirect to google/twitter/...
login_at(params[:provider])
end
def callback
# callback from provider
provider = params[:provider]
if #user = login_from(provider)
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user
}
render 'oauth/complete'
else
# user has no account, create a new one
#user = User.new
#user.email = #user_hash[:user_info]['email']
#user.authentications.build(:uid => #user_hash[:uid], :provider => params[:provider])
#user.oauth_pending!
if #user.save
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user,
errors: #user.errors
}
render 'oauth/complete'
else
render 'oauth/error'
end
end
end
end
There's an example in the Ember.SimpleAuth repo that shows how to use Facebook as an external authentication provider: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-external-oauth/index.html. It basically does it the same way Discourse does it or was doing it 2-3 months ago (not sure whether they changed it) while it doesn't require a server side session.
Supporting Google would work basically the same way.
I updated the Facebook Auth example so that the server part is optional - it now uses the Facebook JS SDK: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-facebook-auth.html. Maybe that help you to get an understanding how Google Auth could be implemented - I'm sure it's going to work quite similarly.
Have you seen the ember-simple-auth project? It supports OAuth2.
I am having trouble figuring out how to make a request to the Google Contacts API after a successful authentication.
provider :google_oauth2, "key", "secret", :scope => "userinfo.profile,https://www.google.com/m8/feeds", :approval_prompt => 'auto'
This successfully authenticates the user with permission to access the contact api.
My question is - how do I make the next request to actually get the contacts using the refresh token and other information provided by:
request.env['omniauth.auth']
I've read that you need to specify specific headers like GData-Version => 3.0 but can't seem to figure out how the entire request would look like.
Thank you!