Table of Contents
Preface
Who is the book for?
Are you new to Go and taking part in the Go Challenge? Some of the challenges might require you to create a web app in Go that uses OAuth to access a resource on say GitHub or Flickr etc. Don’t know how? If yes, then this eBook is for you and will show you how it’s done.
What will you learn?
In the end, you will understand and know how to access GitHub using Go.
Using Code Examples
All of the code in this book can be used pretty much anywhere and anyhow you please.
How to Contact Me
I can be reached via e-mail at golangchallenge@gmail.com. Please contact me if you have any questions, comments, kudos or criticism on the eBook. Constructive criticism is definitely appreciated; I want this eBook to get better through your feedback.
Thanks
Thanks for downloading and checking out this eBook. As part of the lean publishing philosophy, you’ll be able to interact with me as the eBook is completed. I’ll be able to change things, reorganize parts, and generally make a better eBook. I hope you enjoy.
Reference
I have based this free eBook, targetted towards Go newbies, on Krzysztof Kowalczyk’s excellent article.
Accessing GitHub using Go
GitHub, like many other sites, uses OAuth 2.0 protocol for authentication. OAuth2 is a protocol that lets external apps request authorization to private details in a user’s GitHub account without getting their password. Applications that need to read or write private information using the API on behalf of another user should use OAuth.
GitHub API
First, read thro’ the GitHub API documentation.
3-legged authorization
On a conceptual level it works in the following way:
- Client has signed up to the GitHub server and got his client credentials (also known as consumer key and secret) ahead of time
- User wants to give the client access to his protected resources on the server
- Client retrieves the temporary credentials (also known as “request token”) from the server
- Client redirects the resource owner to the server
- Resource owner grants the client access to his protected resources on the server
- Server redirects the user back to the client
- Client uses the temporary credentials to retrieve the token credentials (also known as “access token”) from the server
- Client uses the token credentials to access the protected resources on the server
Register your app
Log into your GitHub a/c and register your app at https://github.com/settings/applications. Click on the “Register new application” button. A registered OAuth application is assigned a unique Client ID and Client Secret. The Client Secret should not be shared. While registering, you can fill out every piece of information however you like, except the Authorization callback URL. This is easily the most important piece to setting up your application. It’s the callback URL that GitHub returns the user to, after successful authentication.
I have registered my app githuboa.go
at my GitHub a/c.
Let’s build our app githuboa.go
We shall be using oauth2 a Go package that contains a client implementation for the OAuth 2.0 specification.
It will be useful if you read the documentation for oauth2 and the OAuth documentation of GitHub.
Now let’s install oauth2
and the github
packages.
1
go
get
golang
.
org
/
x
/
oauth2
2
go
get
github
.
com
/
google
/
go
-
github
/
github
The flow of our app githuboa.go
- the user is on your website and clicks “Log into GitHub” link
- you redirect the user to GitHub’s authorization page. In that url you specify desired access level and a random secret
- the user authorizes your app by clicking on a link
- GitHub redirects to a callback url on your website (which you provided when registering the app with GitHub)
- in the url handler, extract “secret” and “code” arguments
- you have to check that the secret is the same as the one you sent to GitHub (security measure that prevents forgery)
- you call another GitHub url to exchange code for access token
Access token is what you use to authenticate your API calls and allows you to make requests to the API on a behalf of a user.
Code: Create an OAuth config object
1
package
main
2
3
import
(
4
"golang.org/x/oauth2"
5
githuboauth
"golang.org/x/oauth2/github"
6
)
7
8
var
(
9
//
You
must
register
the
app
at
https
:
//
github
.
com
/
settings
/
applications
10
//
Set
callback
to
http
:
//
127.0
.
0.1
:
7000
/
githuboa_cb
11
//
Set
ClientId
and
ClientSecret
to
the
values
you
got
12
//
after
registering
your
app
13
oauthConf
=
&
oauth2
.
Config
{
14
ClientID
:
""
,
//
please
enter
your
value
15
ClientSecret
:
""
,
//
please
enter
your
value
16
//
Comma
separated
list
of
scopes
17
//
select
level
of
access
you
want
https
:
//
developer
.
github
.
com
/
v3
/
oauth
/
#scopes
18
Scopes
:
[]
string
{
"user:email"
},
19
Endpoint
:
githuboauth
.
Endpoint
,
20
}
21
//
An
unguessable
random
string
.
It
is
used
to
protect
against
22
//
cross
-
site
request
forgery
attacks
23
oauthStateString
=
"arandomstring"
24
)
25
26
func
main
()
{
27
}
Config used above describes a typical 3-legged OAuth2 flow, with both the client application information and the server’s endpoint URLs. The Endpoint used above is defined here.
Your main html page
1
package
main
2
3
import
(
4
"fmt"
5
"net/http"
6
7
"golang.org/x/oauth2"
8
githuboauth
"golang.org/x/oauth2/github"
9
)
10
11
const
htmlIndex
=
`
<
html
><
body
><
p
>
Well
,
hello
there
!
</
p
>
12
<
p
>
We
're going to now talk to the GitHub API. Ready?</p>
13
<
p
>
Log
into
<
a
href
=
"/login"
>
GitHub
</
a
></
p
>
14
</
body
></
html
>
15
`
16
17
var
(
18
//
You
must
register
the
app
at
https
:
//
github
.
com
/
settings
/
applications
19
//
Set
callback
to
http
:
//
127.0
.
0.1
:
7000
/
githuboa_cb
20
//
Set
ClientId
and
ClientSecret
to
the
values
you
got
21
//
after
registering
your
app
22
oauthConf
=
&
oauth2
.
Config
{
23
ClientID
:
""
,
//
please
enter
your
value
24
ClientSecret
:
""
,
//
please
enter
your
value
25
//
Comma
separated
list
of
scopes
26
//
select
level
of
access
you
want
https
:
//
developer
.
github
.
com
/
v3
/
oauth
/
#scopes
27
Scopes
:
[]
string
{
"user:email"
},
28
Endpoint
:
githuboauth
.
Endpoint
,
29
}
30
//
An
unguessable
random
string
.
It
is
used
to
protect
against
31
//
cross
-
site
request
forgery
attacks
32
oauthStateString
=
"arandomstring"
33
)
34
35
func
main
()
{
36
http
.
HandleFunc
(
"/"
,
handleMain
)
37
38
fmt
.
Print
(
"Started running on http://127.0.0.1:7000
\n
"
)
39
fmt
.
Println
(
http
.
ListenAndServe
(
":7000"
,
nil
))
40
}
41
42
func
handleMain
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
43
w
.
Header
()
.
Set
(
"Content-Type"
,
"text/html; charset=utf-8"
)
44
w
.
WriteHeader
(
http
.
StatusOK
)
45
w
.
Write
([]
byte
(
htmlIndex
))
46
}
Refer to the ResponseWriter interface where the functions Header()
, WriteHeader()
and Write()
are mentioned. Check the details of the Set method.
Login to GitHub
Once the user clicks on the “Log into GitHub” link the handler for /login url, redirects to GitHub’s authorization page. GitHub will show the authorization page to your user. If the user authorizes your app, GitHub will re-direct to OAuth callback. Here’s how you can turn it into a token, token into http client and use that client to list GitHub information about the user.
1
package
main
2
3
import
(
4
"fmt"
5
"net/http"
6
7
"golang.org/x/oauth2"
8
githuboauth
"golang.org/x/oauth2/github"
9
"html/template"
10
)
11
12
const
htmlIndex
=
`
<
html
><
body
><
p
>
Well
,
hello
there
!
</
p
>
13
<
p
>
We
're going to now talk to the GitHub API. Ready?</p>
14
<
p
>
Log
into
<
a
href
=
"/login"
>
GitHub
</
a
></
p
>
15
</
body
></
html
>
16
`
17
18
var
userInfoTemplate
=
template
.
Must
(
template
.
New
(
""
)
.
Parse
(
`
19
<
html
><
body
>
20
<
p
>
This
app
is
now
authenticated
to
access
your
GitHub
user
info
.</
p
>
21
<
p
>
User
details
are
:
</
p
><
p
>
22
{{
.
}}
23
</
p
>
24
<
p
>
That
's it!</p>
25
</
body
></
html
>
26
`
))
27
28
var
(
29
//
You
must
register
the
app
at
https
:
//
github
.
com
/
settings
/
applications
30
//
Set
callback
to
http
:
//
127.0
.
0.1
:
7000
/
githuboa_cb
31
//
Set
ClientId
and
ClientSecret
to
the
values
you
got
32
//
after
registering
your
app
33
oauthConf
=
&
oauth2
.
Config
{
34
ClientID
:
""
,
//
please
enter
your
value
35
ClientSecret
:
""
,
//
please
enter
your
value
36
//
Comma
separated
list
of
scopes
37
//
select
level
of
access
you
want
https
:
//
developer
.
github
.
com
/
v3
/
oauth
/
#scopes
38
Scopes
:
[]
string
{
"user:email"
},
39
Endpoint
:
githuboauth
.
Endpoint
,
40
}
41
//
An
unguessable
random
string
.
It
is
used
to
protect
against
42
//
cross
-
site
request
forgery
attacks
43
oauthStateString
=
"arandomstring"
44
)
45
46
func
main
()
{
47
http
.
HandleFunc
(
"/"
,
handleMain
)
48
http
.
HandleFunc
(
"/login"
,
handleGitHubLogin
)
49
http
.
HandleFunc
(
"/githuboa_cb"
,
handleGitHubCallback
)
50
51
fmt
.
Print
(
"Started running on http://127.0.0.1:7000
\n
"
)
52
fmt
.
Println
(
http
.
ListenAndServe
(
":7000"
,
nil
))
53
}
54
55
func
handleMain
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
56
w
.
Header
()
.
Set
(
"Content-Type"
,
"text/html; charset=utf-8"
)
57
w
.
WriteHeader
(
http
.
StatusOK
)
58
w
.
Write
([]
byte
(
htmlIndex
))
59
}
60
61
//
/
login
62
func
handleGitHubLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
63
url
:
=
oauthConf
.
AuthCodeURL
(
oauthStateString
,
oauth2
.
AccessTypeOnline
)
64
http
.
Redirect
(
w
,
r
,
url
,
http
.
StatusTemporaryRedirect
)
65
}
66
67
//
githuboa_cb
.
Called
by
github
after
authorization
is
granted
68
func
handleGitHubCallback
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
69
//
If
the
user
accepts
your
request
,
GitHub
redirects
back
70
//
to
your
site
with
a
temporary
code
in
a
code
parameter
71
//
as
well
as
the
state
you
provided
in
the
previous
step
72
//
in
a
state
parameter
.
If
the
states
don
't match, the
73
//
request
has
been
created
by
a
third
party
and
the
process
74
//
should
be
aborted
.
75
state
:
=
r
.
FormValue
(
"state"
)
76
if
state
!=
oauthStateString
{
77
fmt
.
Printf
(
"invalid oauth state, expected '
%s
', got '
%s
'
\n
"
,
oauthStateString
,
state
)
78
http
.
Redirect
(
w
,
r
,
"/"
,
http
.
StatusTemporaryRedirect
)
79
return
80
}
81
82
code
:
=
r
.
FormValue
(
"code"
)
83
84
//
On
success
,
exchange
this
for
an
access
token
85
token
,
err
:
=
oauthConf
.
Exchange
(
oauth2
.
NoContext
,
code
)
86
if
err
!=
nil
{
87
fmt
.
Printf
(
"oauthConf.Exchange() failed with '
%s
'
\n
"
,
err
)
88
http
.
Redirect
(
w
,
r
,
"/"
,
http
.
StatusTemporaryRedirect
)
89
return
90
}
91
92
oauthClient
:
=
oauthConf
.
Client
(
oauth2
.
NoContext
,
token
)
93
94
//
https
:
//
godoc
.
org
/
github
.
com
/
google
/
go
-
github
/
github
95
client
:
=
github
.
NewClient
(
oauthClient
)
96
97
user
,
_
,
err
:
=
client
.
Users
.
Get
(
""
)
98
if
err
!=
nil
{
99
fmt
.
Printf
(
"client.Users.Get() failed with '
%s
'
\n
"
,
err
)
100
http
.
Redirect
(
w
,
r
,
"/"
,
http
.
StatusTemporaryRedirect
)
101
return
102
}
103
104
buf
:
=
[]
string
{
"GitHub login id: "
,
*
user
.
Login
,
"| GitHub email id: "
,
*
user
.
Email
}
105
106
userInfoTemplate
.
Execute
(
w
,
buf
)
107
}
Read the details of AuthCodeURL. AuthCodeURL
returns a URL
to OAuth 2.0 provider’s consent page that asks for permissions for the required scopes explicitly. State is a token to protect the user from CSRF attacks. You must always provide a non-zero string. Opts may include AccessTypeOnline
or AccessTypeOffline
, as well as ApprovalForce
.
The Exchange method above converts an authorization code into a token. It is used after a resource provider redirects the user back to the Redirect URI (the URL
obtained from AuthCodeURL
). The HTTP client to use is derived from the context. If a client is not provided via the context, http.DefaultClient
is used. The code will be in the *http.Request.FormValue("code")
. Before calling Exchange
, be sure to validate FormValue("state")
.
The Client method above returns an HTTP client using the provided token. The token will auto-refresh as necessary. The underlying HTTP transport will be obtained using the provided context. The returned client and its Transport should not be modified.
client := github.NewClient(oauthClient)
constructs a new GitHub client, then uses the various services on the client to access different parts of the GitHub API.
user, _, err := client.Users.Get("")
- see details here fetches a user. Passing the empty string will fetch the authenticated user.
You can get all the details about a User.
Run the above program on http://127.0.0.1:7000.
That’s it!
You can download the entire program from here.
Exercise
How about writing an app that accesses Flickr using OAuth?