Skip to content

Commit ba91d41

Browse files
committed
Merge pull request github#221 from github/guides
Add Guides content
2 parents 9222950 + d01eb94 commit ba91d41

File tree

9 files changed

+692
-70
lines changed

9 files changed

+692
-70
lines changed

Rules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ end
3232
end
3333
end
3434

35+
compile '/guides/*' do
36+
filter :kramdown, :toc_levels => [2]
37+
filter :erb
38+
layout 'default'
39+
end
40+
3541
compile '*' do
3642
filter :erb
3743
filter :kramdown, :toc_levels => [2]
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
---
2+
title: Basics of Authentication | GitHub API
3+
---
4+
5+
# Basics of Authentication
6+
7+
* TOC
8+
{:toc}
9+
10+
In this section, we're going to focus on the basics of authentication. Specifically,
11+
we're going to create a Ruby server (using [Sinatra][Sinatra]) that implements
12+
the [web flow][webflow] of an application in several different ways.
13+
14+
Note: you can download the complete source code for this project [from the platform-samples repo](https://github.com/github/platform-samples/tree/master/api/ruby/basics-of-authentication).
15+
16+
## Registering your app
17+
18+
First, you'll need to [register your
19+
application](https://github.com/settings/applications/new) application. Every
20+
registered OAuth application is assigned a unique Client ID and Client Secret.
21+
The Client Secret should not be shared! That includes checking the string
22+
into your repository.
23+
24+
You can fill out every piece of information however you like, except the
25+
**Authorization callback URL**. This is easily the most important piece to setting
26+
up your application. It's the callback URL that GitHub returns the user to after
27+
successful authentication.
28+
29+
Since we're running a regular Sinatra server, the location of the local instance
30+
is set to `http://localhost:4567`. Let's fill in the callback URL as `http://localhost:4567/callback`.
31+
32+
## Accepting user authorization
33+
34+
Now, let's start filling out our simple server. Create a file called _server.rb_ and paste this into it:
35+
36+
require 'sinatra'
37+
require 'rest_client'
38+
39+
CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
40+
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']
41+
42+
get '/' do
43+
erb :index, :locals => {:client_id => CLIENT_ID}
44+
end
45+
46+
Your client ID and client secret keys come from [your application's configuration page](https://github.com/settings/applications). You should **never, _ever_** store these values in
47+
GitHub--or any other public place, for that matter. We recommend storing them as
48+
[environment variables][about env vars]--which is exactly what we've done here.
49+
50+
Next, in _views/index.erb_, paste this content:
51+
52+
53+
<html>
54+
<head>
55+
</head>
56+
<body>
57+
<p>Well, hello there!</p>
58+
<p>We're going to now talk to the GitHub API. Ready? <a href="https://github.com/login/oauth/authorize?client_id=<%= client_id %>">Click here</a> to begin!</a></p>
59+
<p>If that link doesn't work, remember to provide your own <a href="http://developer.github.com/v3/oauth/#web-application-flow">Client ID</a>!</p>
60+
</body>
61+
</html>
62+
63+
(If you're unfamiliar with how Sinatra works, we recommend [reading the Sinatra guide][Sinatra guide].)
64+
65+
Obviously, you'll want to change `<your_client_id>` to match your actual Client ID.
66+
67+
Navigate your browser to `http://localhost:4567`. After clicking on the link, you
68+
should be taken to GitHub, and presented with a dialog that looks something like this:
69+
![](/images/oauth_prompt.png)
70+
71+
If you trust yourself, click **Authorize App**. Wuh-oh! Sinatra spits out a
72+
`404` error. What gives?!
73+
74+
Well, remember when we specified a Callback URL to be `callback`? We didn't provide
75+
a route for it, so GitHub doesn't know where to drop the user after they authorize
76+
the app. Let's fix that now!
77+
78+
### Providing a callback
79+
80+
In _server.rb_, add a route to specify what the callback should do:
81+
82+
get '/callback' do
83+
# get temporary GitHub code...
84+
session_code = request.env['rack.request.query_hash']["code"]
85+
# ... and POST it back to GitHub
86+
result = RestClient.post("https://github.com/login/oauth/access_token",
87+
{:client_id => CLIENT_ID,
88+
:client_secret => CLIENT_SECRET,
89+
:code => session_code},
90+
:accept => :json)
91+
access_token = JSON.parse(result)["access_token"]
92+
end
93+
94+
After a successful app authentication, GitHub provides a temporary `code` value.
95+
You'll need to `POST` this code back to GitHub in exchange for an `access_token`.
96+
To simplify our GET and POST HTTP requests, we're using the [rest-client][REST Client].
97+
Note that you'll probably never access the API through REST. For a more serious
98+
application, you should probably use [a library written in the language of your choice][libraries].
99+
100+
At last, with this access token, you'll be able to make authenticated requests as
101+
the logged in user:
102+
103+
auth_result = RestClient.get("https://api.github.com/user", {:params => {:access_token => access_token}})
104+
105+
erb :basic, :locals => {:auth_result => auth_result}
106+
107+
We can do whatever we want with our results. In this case, we'll just dump them straight into _basic.erb_:
108+
109+
<p>Okay, here's a JSON dump:</p>
110+
<p>
111+
<p>Hello, <%= login %>! It looks like you're <%= hire_status %>.</p>
112+
</p>
113+
114+
## Implementing "persistent" authentication
115+
116+
It'd be a pretty bad model if we required users to log into the app every single
117+
time they needed to access the web page. For example, try navigating directly to
118+
`http://localhost:4567/basic`. You'll get an error.
119+
120+
What if we could circumvent the entire
121+
"click here" process, and just _remember_ that, as log as the user's logged into
122+
GitHub, they should be able to access this application? Hold on to your hat,
123+
because _that's exactly what we're going to do_.
124+
125+
Our little server above is rather simple. In order to wedge in some intelligent
126+
authentication, we're going to switch over to implementing [a Rack layer][rack guide]
127+
into our Sinatra app. On top of that, we're going to be using a middleware called
128+
[sinatra-auth-github][sinatra auth github] (which was written by a GitHubber).
129+
This will make authentication transparent to the user.
130+
131+
After you run `gem install sinatra_auth_github`, create a file called _advanced_server.rb_,
132+
and paste these lines into it:
133+
134+
require 'sinatra/auth/github'
135+
require 'rest_client'
136+
137+
module Example
138+
class MyBasicApp < Sinatra::Base
139+
# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
140+
# Instead, set and test environment variables, like below
141+
# if ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET']
142+
# CLIENT_ID = ENV['GITHUB_CLIENT_ID']
143+
# CLIENT_SECRET = ENV['GITHUB_CLIENT_SECRET']
144+
# end
145+
146+
CLIENT_ID = ENV['GH_BASIC_CLIENT_ID']
147+
CLIENT_SECRET = ENV['GH_BASIC_SECRET_ID']
148+
149+
enable :sessions
150+
151+
set :github_options, {
152+
:scopes => "user",
153+
:secret => CLIENT_SECRET,
154+
:client_id => CLIENT_ID,
155+
:callback_url => "/callback"
156+
}
157+
158+
register Sinatra::Auth::Github
159+
160+
get '/' do
161+
if !authenticated?
162+
authenticate!
163+
else
164+
access_token = github_user["token"]
165+
auth_result = RestClient.get("https://api.github.com/user", {:params => {:access_token => access_token, :accept => :json},
166+
:accept => :json})
167+
168+
auth_result = JSON.parse(auth_result)
169+
170+
erb :advanced, :locals => {:login => auth_result["login"],
171+
:hire_status => auth_result["hireable"] ? "hireable" : "not hireable"}
172+
end
173+
end
174+
175+
get '/callback' do
176+
if authenticated?
177+
redirect "/"
178+
else
179+
authenticate!
180+
end
181+
end
182+
end
183+
end
184+
185+
Much of the code should look familiar. For example, we're still using `RestClient.get`
186+
to call out to the GitHub API, and we're still passing our results to be renderend
187+
in an ERB template (this time, it's called `advanced`). Some of the other details--
188+
like turning our app into a class that inherits from `Sinatra::Base`, are a result
189+
of inheriting from `sinatra/auth/github`, which written as [a Sinatra extension][sinatra extension].
190+
191+
Also, we now have a `github_user` object, which comes from `sinatra-auth-github`. The
192+
`token` key represents the same `access_token` we used during our simple server.
193+
194+
`sinatra-auth-github` comes with quite a few options that you can customize. Here,
195+
we're establishing them through the `:github_options` symbol. Passing your client ID
196+
and client secret, and calling `register Sinatra::Auth::Github`, is everything you need
197+
to simplify your authentication.
198+
199+
We must also create a _config.ru_ config file, which Rack will use for its configuration
200+
options:
201+
202+
ENV['RACK_ENV'] ||= 'development'
203+
require "rubygems"
204+
require "bundler/setup"
205+
206+
require File.expand_path(File.join(File.dirname(__FILE__), 'advanced_server'))
207+
208+
run Example::MyBasicApp
209+
210+
Next, create a file in _views_ called _advanced.erb_, and paste this markup into it:
211+
212+
<html>
213+
<head>
214+
</head>
215+
<body>
216+
<p>Well, well, well, <%= login %>! It looks like you're <em>still</em> <%= hire_status %>!</p>
217+
</body>
218+
</html>
219+
220+
From the command line, call `rackup -p 4567`, which starts up your
221+
Rack server on port `4567`--the same port we used when we had a simple Sinatra app.
222+
When you navigate to `http://localhost:4567`, the app calls `authenticate!`--another
223+
internal `sinatra-auth-github` method--which redirects you to `/callback`. `/callback`
224+
then sends us back to `/`, and since we've been authenticated, renders _advanced.erb_.
225+
226+
We could completely simplify this roundtrip routing by simply changing our callback
227+
URL in GitHub to `/`. But, since both _server.rb_ and _advanced.rb_ are relying on
228+
the same callback URL, we've got to do a little bit of wonkiness to make it work.
229+
230+
Also, if we had never authorized this Rack application to access our GitHub data,
231+
we would've seen the same confirmation dialog from earlier pop-up and warn us.
232+
233+
If you'd like, you can play around with [yet another Sinatra-GitHub auth example][sinatra auth github test]
234+
available as a seperate project.
235+
236+
[webflow]: http://developer.github.com/v3/oauth/#web-application-flow
237+
[Sinatra]: http://www.sinatrarb.com/
238+
[about env vars]: http://en.wikipedia.org/wiki/Environment_variable#Getting_and_setting_environment_variables
239+
[Sinatra guide]: http://sinatra-book.gittr.com/#hello_world_application
240+
[REST Client]: https://github.com/archiloque/rest-client
241+
[libraries]: http://developer.github.com/v3/libraries/
242+
[rack guide]: http://en.wikipedia.org/wiki/Rack_(web_server_interface)
243+
[sinatra auth github]: https://github.com/atmos/sinatra_auth_github
244+
[sinatra extension]: http://www.sinatrarb.com/extensions.html
245+
[sinatra auth github test]: https://github.com/atmos/sinatra-auth-github-test

0 commit comments

Comments
 (0)