Today I helped @stanzheng implement an oauth implementation in Go for his Recurse Center Cli application; as a result I learned the basic flow of oath authentication. Like using JWT, it’s token based. It’s for accessing Protected Resources on a third party’s application. The permission scope, expiry times, and refresh tokens can make for more or less complicated proceedures, but the basic flow is simple enough to grasp.

Token based authentication

Instead of a using sessions or cookies to keep track of a users authentications, tokens are a great solution, albeit they can be a bit more complicated. The upside is that they don’t depend on a browser, so mobile apps, web apps, and even command line apps like @stanzheng’s can work with the same authentication: namely, a server, upon verifying some credentials, signs a token (usually using a pretty badass two-way hash) with some sort of identity (like a user id) and some other informations, such as expirey date and issuer (really, all number of messages/values can be stored in these tokens). Because they are signed with a private key, no one else can forge an token.

With an OAuth token, a 3rd party application can access scoped permissions from a service (such as Github or Twitter), for interacting with a user’s account.

Steps

There are three requests made for the OAuth Protocol flow.

  1. Request a login page at the OAuth authentication URL and redirect the response, with the RequestToken, to an endpoint on our application server
  2. Request for authentication which responds with an AccessToken
  3. The User requests some protected resource using the AccessToken

The OAuth docs provide a nice figure to demonstrate:

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(B)-- Authorization Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- Authorization Grant -->| Authorization |
| Client |                               |     Server    |
|        |<-(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |<-(F)--- Protected Resource ---|               |
+--------+                               +---------------+

This is a simplification (though it works for getting an access token), as the token will expire in a given amount of time, and so the OAuth service should provide a RefreshToken to keep the logged in session going.

The basic variables needed before-hand are as follows:

var (
    // Username and password
    ClientId     = "I Am A Registered Application"
    ClientSecret = "I Am A Secret"
    // Urls for auth flow
    RedirectUrl = "http://localhost:9991"
    TokenUrl    = "https://www.example.com/oauth/token"
    AuthUrl     = "https://www.example.com/oauth/authorize"
)

Get the Request Token

We wrote this in Go, and so to get the RequestToken, we needed to construct a URL with queries including, namely, the Client ID and the URL for redirections:

u, _ := url.Parse(AuthUrl)
uq := u.Query()
uq.Add("response_type", "code")     // We want the request token
uq.Add("client_id", ClientId)       // Registered with the service
uq.Add("redirect_uri", RedirectUrl) // The endpoint you're listening on
uq.Add("access_type", "online")
u.RawQuery = uq.Encode()

After the user visits this URL, they’ll be greeted with a login page elsewhere, and that page will redirect as indicated in the request query.

The response will provide a code, or RequestToken, which you can parse from the response query parameters:

    requestToken := r.URL.Query().Get("code")

Get the AccessToken

// Construct Request Parameters
u, _ := url.Parse(TokenUrl)
uq := u.Query()
uq.Add("grant_type", "authorization_code")
uq.Add("code", requestToken)
uq.Add("redirect_uri", RedirectUrl)
u.RawQuery = uq.Encode()

// Build Request and POST for Response
hc := http.Client{}
req, _ := http.NewRequest("POST", u.String(), nil)
req.Header.Add("Content-Type", "application/x-www.form-urlencoded")
req.SetBasicAuth(ClientId, ClientSecret) // net method for base64 encoding
resp, _ := hc.Do(req)

// Decode json into AccessToken Struct
accessToken := new(AccessToken)
_ = json.NewDecoder(resp.Body).Decode(accessToken)

return accessToken

Access Protected Resources

Thereafter it’s as simple as making HTTP requests using the AccessToken for authentication; doing things like posting on Twitter from a user’s account, or getting a user’s private repository information from Github will now work without the need for storing a password. Bingo.

The whole code:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
)

type AccessToken struct {
    AccessToken  string `json:"access_token"`
    TokenType    string `json:"token_type"`
    ExpiresIn    int    `json:"expires_in"`
    RefreshToken string `json:"refresh_token"`
    Scope        string `json:"scope"`
    CreatedAt    int    `json:"created_at"`
}

var (
    // Username and password
    ClientId     = "I Am A Registered Application"
    ClientSecret = "I Am A Secret"
    // Urls for auth flow
    RedirectUrl = "http://localhost:9991"
    TokenUrl    = "https://www.example.com/oauth/token"
    AuthUrl     = "https://www.example.com/oauth/authorize"
    // Access Token
    accessToken *AccessToken
)

func main() {
    // Url for the login page with 3rd party service
    u, _ := url.Parse(AuthUrl)
    uq := u.Query()
    uq.Add("response_type", "code")     // We want the request token
    uq.Add("client_id", ClientId)       // Registered with the service
    uq.Add("redirect_uri", RedirectUrl) // The endpoint you're listening on
    uq.Add("access_type", "online")
    u.RawQuery = uq.Encode()

    go func() {
        fmt.Println("Listening on port 9991")
        http.ListenAndServe(":9991", nil) // OAuth service will interact here
    }()

    // User link to "Login Page" (get the request token)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "a href=%s>login</a>", u)
    })

    // Rediects request token to the busy part
    http.HandleFunc("authenticated", func(w http.ResponseWriter, r *http.Request) {
        requestToken := r.URL.Query().Get("code")
        accessToken = Authorize(requestToken)
        fmt.Fprint(w, accessToken.AccessToken)
        // Make requests with this now
        // and be 'logged in'
    })
}

// Authorize recieves request token, from user logging interact
// in the service's OAuth page.
func Authorize(requestToken string) *AccessToken {
    // Construct Request Parameters
    u, _ := url.Parse(TokenUrl)
    uq := u.Query()
    uq.Add("grant_type", "authorization_code")
    uq.Add("code", requestToken)
    uq.Add("redirect_uri", RedirectUrl)
    u.RawQuery = uq.Encode()

    // Build Request and POST for Response
    hc := http.Client{}
    req, _ := http.NewRequest("POST", u.String(), nil)
    req.Header.Add("Content-Type", "application/x-www.form-urlencoded")
    req.SetBasicAuth(ClientId, ClientSecret) // net method for base64 encoding
    resp, _ := hc.Do(req)

    // Decode json into AccessToken Struct
    accessToken := new(AccessToken)
    _ = json.NewDecoder(resp.Body).Decode(accessToken)

    return accessToken
}