OAuth2 is an authentication protocol that is used to authenticate and authorize users in an application by using another service provider.
This post will go through how to build a Go application to implement the OAuth2 protocol.
If you just want to see the code, you can view it here
OAuth2 flow
Let’s take a brief look at the OAuth protocol before we jump into implementation. If you’ve ever seen a dialog similar to this, then you probably have some idea of what OAuth is:
In the above image, we are trying to login to Gitlab using Github to authenticate.
There are three parties in any OAuth flow:
- The client - The person, or user who is logging in
- The consumer - The application that the client wants to log in to (which was gitlab in the above image)
- The service provider - The external application through which the user authenticates. (which was github in the above image)
In this post, we will authenticate using Githubs OAuth2 API, and build a sample Go application, running on the local port 8080, with a web interface. So in our case, the client would be the web interface, the consumer would be the application
running on
localhost:8080
and the service provider would be Github. Let’s look at how this would all work:
We can implement each part of the flow in our application.
Landing page
Lets create the first part of the application, which is the landing page. This will be a simple HTML page, with a link that the user should click on to authenticate with Github. The following will go into a file
public/index.html
:
<!DOCTYPE html>
<html>
<body>
<a href="https://github.com/login/oauth/authorize?client_id=myclientid123&redirect_uri=http://localhost:8080/oauth/redirect">
Login with github
</a>
</body>
</html>
The above link has three key parts:
-
https//github.com/login/oauth/authorize
is the OAuth gateway for Github’s OAuth flow. All OAuth providers have a gateway URL that you have to send the user to in order to proceed. -
client_id=myclientid123
- this specifies the client ID of the application. This ID will tell Github about the identity of the consumer who is trying to use their OAuth service. OAuth service providers have portal in which you can register your consumer. On registration, you will receive a client ID (which we are using here asmyclientid123
), and a client secret (which we will use later on). For Github, the portal to register new applications can be found on github.com/settings/ap… . After -
redirect_uri=http://localhost:8080/oauth/redirect
- specifies the URL to redirect to with the request token, once the user has been authenticated by the service provider. Normally, you will have to set this value on the registration portal as well, to prevent anyone from setting malicious callback URLs.
Next, we need to serve the
index.html
file we made above. The following code would go into a new file
main.go
:
func main() {
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
http.ListenAndServe(":8080", nil)
}
In the current state, you can start the server (by executing
go run main.go
) and visit
http://localhost:8080
, and you will see the landing page we just made. Once you click on the “Login with github”
link, you will be redirected to the familiar OAuth page to register with Github. However, once you authenticate, you will be redirected to
http://localhost:8080/oauth/redirect
, which at the moment, does not do anything, and will
lead to a 404 page on the server.
The redirect route
Once the user authenticates with Github, they get redirected to the redirect URL that was specified earlier. The service provider also adds a request token along with the url. In this case, Github adds this as the
code
parameter,
so the redirect URL will actually be something like
http://localhost:8080/oauth/redirect?code=mycode123
, where
mycode123
the request token. We need this request token, and our client secret to get the
access token
,
which is the token that is actually used to get information about the user. We get this access token by making a
POST
HTTP call to
https://github.com/login/oauth/access_token
along with the mentioned information.
The complete documentation on the information Github provides to the redirect URL, and the information we need for provide with the
POST /login/oauth/access_token
HTTP call, can be found
here
.
Let’s add to the
main.go
file, to handle the
/oauth/redirect
route:
const clientID = "<your client id>"
const clientSecret = "<your client secret>"
func main() {
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// We will be using `httpClient` to make external HTTP requests later in our code
httpClient := http.Client{}
// Create a new redirect route route
http.HandleFunc("/oauth/redirect", func(w http.ResponseWriter, r *http.Request) {
// First, we need to get the value of the `code` query param
err := r.ParseForm()
if err != nil {
fmt.Fprintf(os.Stdout, "could not parse query: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
code := r.FormValue("code")
// Next, lets for the HTTP request to call the github oauth enpoint
// to get our access token
reqURL := fmt.Sprintf("https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s", clientID, clientSecret, code)
req, err := http.NewRequest(http.MethodPost, reqURL, nil)
if err != nil {
fmt.Fprintf(os.Stdout, "could not create HTTP request: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
// We set this header since we want the response
// as JSON
req.Header.Set("accept", "application/json")
// Send out the HTTP request
res, err := httpClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stdout, "could not send HTTP request: %v", err)
w.WriteHeader(http.StatusInternalServerError)
}
defer res.Body.Close()
// Parse the request body into the `OAuthAccessResponse` struct
var t OAuthAccessResponse
if err := json.NewDecoder(res.Body).Decode(&t); err != nil {
fmt.Fprintf(os.Stdout, "could not parse JSON response: %v", err)
w.WriteHeader(http.StatusBadRequest)
}
// Finally, send a response to redirect the user to the "welcome" page
// with the access token
w.Header().Set("Location", "/welcome.html?access_token="+t.AccessToken)
w.WriteHeader(http.StatusFound)
})
http.ListenAndServe(":8080", nil)
}
type OAuthAccessResponse struct {
AccessToken string `json:"access_token"`
}
Now, the redirect URL, if functional, and will redirect the user to the welcome page, along with the access token.
Welcome page
The welcome page is the page we show the user after they have logged in. Now that we have the users access token, we can obtain their account information on their behalf as authorized Github users.
For a list of all APIs available, you can see the Github API Documentation
We will be using the
/user
API to get basic info about the user and say hi to them on our welcome page. Create a new file
public/welcome.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
</body>
<script>
// We can get the token from the "access_token" query
// param, available in the browsers "location" global
const query = window.location.search.substring(1)
const token = query.split('access_token=')[1]
// Call the user info API using the fetch browser library
fetch('//api.github.com/user', {
headers: {
// Include the token in the Authorization header
Authorization: 'token ' + token
}
})
// Parse the response as JSON
.then(res => res.json())
.then(res => {
// Once we get the response (which has many fields)
// Documented here: https://developer.github.com/v3/users/#get-the-authenticated-user
// Write "Welcome <user name>" to the documents body
const nameNode = document.createTextNode(`Welcome, ${res.name}`)
document.body.appendChild(nameNode)
})
</script>
</html>
With the addition of the welcome page, our OAuth flow is now complete! Once the app starts, one can go to
http://localhost:8080/
, authorize with Github, and end up on the welcome page, which displays the greeting.
My name on my github profile is “Soham Kamani”, so the weclome page will display
Welcome, Soham Kamani
once I login.