Authentication for Sinatra REST API app - ruby

I'm building an API with Sinatra (using Angular for the client side and want others to have access to API) and have it also be an OAuth provider. I am wondering what the best route to take (work off existing gems or roll own solution off Warden or something).
Have used devise and doorkeeper for authentication and oauth before with Rails, wondering what best solution for Sinatra is.
Ideally I don't want the views or be able to extend/mod the actions of an existing solution, as I'm interacting with it purely as an API.

I just recently did the same thing using the following answer from S/O
What is a very simple authentication scheme for Sinatra/Rack
It implies a user model, but instead of using that, I just set a user and admin password in my config file. Then I had a login form that just took a password. When the user enters that password, I checked it against the one in settings and set the session['user'] to :admin or :user according to whichever it matched (or nil if none). Then on each of my routes, I called auth: :user or auth: :admin accordingly.

APIs normally accept your login request and send you an authentication token which you need to pass back in each call. This is very similar to cookie based sessions where your browser automatically passes back the cookie which is acquired on initial website visit.
From what I've seen in Sinatra's docs, you could make a session-based authentication system like this:
enable :session
disable :show_exceptions
use Rack::Session::Pool,
key: 'session_id'
post '/login' do
user = User.login_success(params)
halt 401 if user.nil?
session[:user] = user
200
end
get '/fun' do
user = session[:user]
halt 401 if user.nil?
halt 403 if !user.has_permission_for '/fun'
"fun was had"
end
Now all you need to do in your client is to pass back the cookie token returned in response to initial visit when requesting an API function. This can be done with any web client library that supports cookie stores (such as libcurl) or by inserting the session cookie into the request header manually. Rack::Minitest functionality also supports cookies, so you can test your API with minitest.

See Sinatra API Authentication.
Quick summary:
Sinatra has no built-in auth.
It's best to build auth yourself (see the link).
There are gems available, but you probably won't need them for something as simple as an API.

Related

Omniauth Strategy Outside Devise

I've setup devise + omniauth for Google sign in. It works great on the web. I'm now looking at integrating the oauth sign in to iOS / Android clients.
I've been following https://developers.google.com/identity/sign-in/ios/backend-auth and found that the library I'm using for oauth does these things (https://github.com/zquestz/omniauth-google-oauth2/blob/master/lib/omniauth/strategies/google_oauth2.rb).
What I'm not clear about is if I can use a omniauth strategy outside the regular web workflow (i.e. from rails console) to build a pseudo request.env["omniauth.auth"]. Is something like that possible? Per the documentation https://developers.google.com/identity/sign-in/ios/backend-auth#using-a-google-api-client-library I'd like to do the equivalent in Ruby (and am unclear if I can use Devise directly to do this).
Simulating requests in the console is pretty easy. You can easily make requests to the app variable the console provides:
app.get('/') # => 200
app.response # => #<ActionDispatch::TestResponse:0x007fc73e4db220>
As for handling authentication, standard rails applications use cookie/session-based authentication strategies on the web. After you authenticate the first time, some information gets stored in session (often as a cookie) that you and the server will pass back and forth with each request.
Since mobile clients don't rely on cookies, we need a different authentication strategy: token-based authentication.
Here a high-level implementation that would work with Omniauth:
User Requests Access through Omniauth proivder by making requests to the Omniauth endpoint
Application processes the credentials
Application provides a signed token to the client
Client stores that token and sends it along with every request
Server verifies token and responds with data
For handling mobile requests, you'll need to be careful to follow the fine print for the provider's Omniauth gem.
Token authentication used to be baked into Devise, but it was removed. Thankfully, there are a few gems that add token auth to Devise:
simple_token_authentication
devise_token_auth

Ruby OAuth2 client

I'm trying to create a script to access the Quizlet API
Those API are protected with OAuth2 and I'm using this oauth2 ruby gem https://github.com/intridea/oauth2
The gem's GitHub page shows an example but for me doesn't work and I feel I'm missing some pieces.
This is part of the example:
client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback')
# => "https://example.org/oauth/authorization?response_type=code&client_id=client_id&redirect_uri=http://localhost:8080/oauth2/callback"
token = client.auth_code.get_token('authorization_code_value', :redirect_uri => 'http://localhost:8080/oauth2/callback', :headers => {'Authorization' => 'Basic some_password'})
Two quetions:
What should I do with the result of authorize_url? This url should be opened on a browser to proceed with the login, but the gem doesn't open it automatically. Should I ask the user to do so?
What about the callback URL? The example uses http://localhost:8080/oauth2/callback but is not clear if the gem itelf is in charge of listening on that port.
Any advices?
It might be easier to look at what the Quizlet API is asking you to do, and skipping the integration with the oauth gem. I could be wrong and I'd be interested to see how the the gem is used, but it's still a good idea to look at how to implement an oauth client step-by-step.
This is taken from the Quizlet API docs:
Send the user to https://quizlet.com/authorize. This can be done by displaying a link for the user to click, or redirecting server-side.
Once the user accepts/denies your oauth (this happens on the quizlet website, not your own), quizlet will send a request to your server. Because of the /authorize call, Quizlet will now have a redirect_uri which they will use to get in touch with your server. In your server's action for this route, you can get the authorization_code.
Send the authorization_code to https://api.quizlet.com/oauth/token, and in the response get an access_token whic you use for the rest of the api requests.
About the gem
You should send the authorize url to users. This is step 1 above
the gem is not in charge of listening for the redirect. You need to use a web server for this.

How to maintain Oauth2 session validity in Sinatra for multiple requests?

I am developing a Sinatra web app to which I plan to add a Google sign in capability.
While I am able to understand the Oauth2 authentication mechanism with Google and everything seems to work fine, I have a basic question on maintaining sessions.
I am using the Server side web application flow of Oauth2.
Before I ask my question here is my understanding of the Oauth2 authentication mechanism.
Once the user clicks on the "Sign in with Google" button, the sequence of events are:
The user is redirected to the Google oauth authorization server.
The Google oauth2 authorization server checks if the user has a active
session.
If yes it prompts the user to grant access to my application for the requested data from Google.
If the user grants access then the process of sending back an auth_code and getting access tokens commences.
Based on what I have encountered on the web, the code for all of the above would have to be put in a before filter.
My questions are below:
If I add this code to a before filter, then there would be a round trip to the Google Authentication server for each request that comes to my application from the user.
Am I thinking right in the statement 1 above?
Is this necessary?
Is there some other way of validating session without reaching out to the Google server for each request to my server?
Will this not cause an overhead?
How do web-apps using oauth2 typically handle checking session validity across multiple requests?
Sorry about the longish question and thanks for your patience.
When the user clicks on the Login link and returns with a valid response i.e request.env["omniauth.auth"], you need to store the "uid" in the session and then check for the session in the next requests. Like this
before do
unless ['/login', '/auth/google_oauth2/callback'].include?(action)
unless session[:uid]
redirect "/login"
end
end
end
get "/auth/google_oauth2/callback" do
session[:uid] = request.env["omniauth.auth"]["uid"]
redirect "/"
end
Let me know if it works.

How to use basic authentication with httparty in a Rails app?

The command line version of 'httparty' with basic authentication works simple and great:
httparty -u username:password http://example.com/api/url
But now I'm looking for the way I can add the basic auth to a HTTParty.get call from within a Rails app. First of all, for testing purposes, I want to hard code the login credentials in the Controller. Just to make sure it works. But I can't find any documentation or examples how you can pass these along.
A HTTParty.get without credentials works fine:
#blah = HTTParty.get("http://twitter.com/statuses/public_timeline.json")
But I don't see how I can make a variation on this that accepts the -u username:password part.
The next challenge for me (am very new to Ruby/Rails) is to get the user credentials from a user form and pass it along dynamically, but most important for me now it to get the hard coded version to work.
auth = {:username => "test", :password => "test"}
#blah = HTTParty.get("http://twitter.com/statuses/public_timeline.json",
:basic_auth => auth)
Two points,
If you are hitting Twitter's api, unless I'm mistaken I don't think they allow basic auth anymore :( So you may want to look into something like OmniAuth for OAuth sign-in. You don't need HTTParty or a sign-in form for this, you link to the Twitter sign-in and the user enters credentials there, then Twitter sends a callback request to your app once authenticated. OmniAuth does most of the work for you, you just pull the info you need out of what it gives you in the callback route.
But even so, you will still need the OAuth 'consumer key' and 'consumer secret' which are specific to your application (how Twitter authorizes your application, as distinguished from the user). And you don't want these, nor any auth keys, in your source code.
A typical way of doing this is stick them into a config/omniauth.yml file which is not checked in to source control:
twitter:
key: CONSUMER_KEY
secret: CONSUMER_SECRET
And then load them in an initializer config/initializers/omniauth.rb :
consumers = YAML.load("#{Rails.root}/config/omniauth.yml")
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter, consumers['twitter']['key'], consumers['twitter']['secret']
end
You could take a similar approach with loading basic auth username/passwords, just stick them in some object that you'll have access to from wherever you make the HTTParty calls.

Ruby twitter client

I'm trying to create a ruby-based twitter client where I can post my status from command-line using ruby. I'm trying to understand the oauth right now, and it confused me a little bit. If I'm building a web application, I can provide a callback url when the request token is complete. How would I do that from the command-line? I don't want ruby to print out the authorized and copy and paste the url and click 'Allow' to get the token. I found something about out-of-band exchange or PIN. How would I do that with OAuth library in ruby, please thank you very much.
To use the callback url mechanism, you application should be a web application. It seems you are developing desktop application and if that's the case, you should follow "PIN code" flow by supplying an oauth_callback_url of "oob" (out-of-band) when you request token. Like this,
https://api.twitter.com/oauth/request_token?oauth_callback=oob
If you properly set a header of this HTTP request (setting HTTP header is the key part of OAuth and I think you already know how to do this), Twitter will give oauth_token, oauth_token_secret and oauth_verifier. Let's call this token "request_token". You need it to get "access_token" later.
Once you have request_token, you need to open web page with the below url
http://api.twitter.com/oauth/authorize?oauth_token=request_token
This will open the authorization page and let a user to decide whether the user wants to allow your application to access his or her Twitter account. If the use says okay, then Twitter gives PIN code. You need to allow a user to type the PIN code so that you can save it.
Now, it's time to get another token ("access_token") by using your comsumer_key / secret, request_token and the PIN code. You should set header with all these values correctly and do HTTP request again with this url,
https://api.twitter.com/oauth/access_token
If Twitter accepts your "access_token" request, it will give you oauth_token, oauth_token_secret, user_id and screen_name. Let's call this token "access_token". Now, you can perform any OAuth required Twitter API by using access_token and its secret (oauth_token_secret). You can save the two values in a file and keep using them whenever you need to access the user's Twitter account. The values will be always valid until the user revokes the access to your application.
I don't know Ruby but if you know how to perform HTTP/HTTPS requests (GET / POST) with custom headers in Ruby, this PIN code flow should work fine if you follow Twitter API document carefully. Good Luck!
It will ask for the PIN code until you specify the oauth_callback when getting the request token, not when forwarding the user to the authorization url
#consumer = OAuth::Consumer.new(
TWITTER_CONSUMER_KEY,
TWITTER_CONSUMER_SECRET,
{:site=>"https://api.twitter.com"})
#request_token = #consumer.get_request_token( :oauth_callback => CALLBACK_URL )
This was the result of surfing several hours of incomplete documentation.
use Twitter gem, it will make things easier for you. http://rdoc.info/gems/tweeter/2.0.0/frames

Resources