Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A minimal Sinatra OAuth 2.0 client and resource server.
require 'sinatra'
require 'open-uri'
require 'json'
# This application is the bare minimum to authorize with OAuth 2.0
# using the authorization grant scheme. No error handling included.
# The application is both a client and a resource server.
# Start it by using 'ruby <file>' and navigate to http://localhost:4567
#
# The application also needs the oauth-server written in Java.
# See https://github.com/comoyo/oauth-server
# Start it with 'mvn jetty:run' for now.
configure do
set client_id: "123456789"
set client_secret: "kakekake"
set oauth_host: "http://localhost:8080/api"
end
# Global variables. So much ugly...
@@access_token = ""
@@redirect_uri = 'http://localhost:4567/oauth/callback'
# Client paths
# Root path. Begin here.
get '/' do
"<a href='/oauth/redirect'>Authorize this app, motherfucker.</a>"
end
# The button on the main page takes the user here.
# Here, we build a redirect uri to the authorization server with
# the client id, state, redirect_uri to our callback and more.
get '/oauth/redirect' do
# Set some easy to read variables.
response_type = "code"
state = "kake"
redirect_uri = @@redirect_uri
# Build redirect uri
uri = settings.oauth_host + "/oauth/auth"
uri += "?client_id=#{settings.client_id}"
uri += "&response_type=#{response_type}"
uri += "&state=#{state}"
uri += "&redirect_uri=#{redirect_uri}"
# Send a 302 Temporary Redirect to the user-agent.
# This will redirect the user to the Authorization server.
redirect uri
end
# The user-agent is redirected here by the oauth-server after the app was given authorization.
# Here, we extract the authorization code and do an internal request to the token endpoint
# to retrieve the access token and optional refresh token.
get '/oauth/callback' do
# Set some easy to read variables.
code = params[:code]
grant_type = "authorization_code"
redirect_uri = @@redirect_uri
# Build request URI
uri = URI(settings.oauth_host + "/oauth/token")
uri.query = "code=#{code}&grant_type=#{grant_type}&redirect_uri=#{redirect_uri}"
# Open an HTTP connection to the authorization server using basic auth.
open(uri, :http_basic_authentication=>[settings.client_id, settings.client_secret]) do |http|
@respons = JSON.parse(http.read)
end
# Extract the access token from the json response.
@@access_token = @respons["access_token"]
# Redirect the user to another url to hide the ugly authcode url.
redirect to('/oauth/finish')
end
# The user has now authorized our app, and the client has procured an access token.
# Here, we just present the user with a simple message and a prompt to fetch a protected resource.
# In reality, the client doesn't need user action to use the access token during the lifetime of the token.
get '/oauth/finish' do
path = "/rs/protected?access_token=#{@@access_token}&client_id=#{settings.client_id}"
"Authorized.<br><a href='#{path}'>Fetch protected resource</a>"
end
# Resource Server paths.
# This is our resource server path. It is protected from everyone that do not have an access token.
# This can usually be a users watched movies, the phone number of the user or similar.
# The resource server MUST validate the token and verify that the audience field matches the supplied client_id.
get '/rs/protected' do
access_token = params[:access_token]
client_id = params[:client_id]
if valid_token?(access_token, client_id)
return "Congratulations! You have access to this token."
end
"You do not have access to this resource."
end
# A convenience method to validate a token by requesting token information from the authorization server.
def valid_token?(token, client_id)
# Build request URI
uri = URI(settings.oauth_host + "/oauth/tokeninfo")
uri.query = "access_token=#{token}"
puts uri.to_s
# Open an HTTP connection to uri
open(uri) do |http|
@respons = JSON.parse(http.read)
end
# Only return true if the server returns successfully, and the audience field matches the client id.
if not @respons["audience"].nil? && @respons["audience"] == client_id
return true
end
return false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment