Tuesday, November 22, 2011

OAuth2: 3-legged authorization in a Go web application


OAuth is currently the recommended standard for user authorization. I’ll limit this write up to just examples of using the OAuth standard with Google using Go. However there are a lot of good write ups on the web that explains the process well.

One of the major uses of OAuth is that a user can authorize your application, using a 3-legged authorization mechanism, to access his/her data from another service without giving you his authentication details. The authentication details are usually one’s username and password but it could be more - additional verification mechanisms like a finger print scan or a retina scan or what have you. I’ll illustrate the intended flow of 3-legged OAuth with a simplified real-world example.

The setting for this story is: user X wants an online application Printr to print a his/her which is in another application called Photor.
1. User X to Printr: "Please print this photo of mine which is currently with Photor."
2. Printr to Photor: "Please grant me access user X’s photo."
3. Photor to user X: "Dear X, Printr is requesting access to your photo. If you are ok with that, can you please authorize it? Here is a webpage given by us where you can Accept or Deny this."
4. User X: "I want Printr to access that picture. Here, I Accept."
5. Photor to Printr: "User X accepted, so here, take this ‘code’ for now."
6. Printr to Photor: "Thanks for this code. Can you now give me a temporary token?"
7. Photor to Printr: "The code that Printr has returned seems legitimate and the user has agreed to grant access. Here, now take this ‘token’ with which you can access that photo."
8. Printr now accesses the photo using the token and prints it for user X.

In the above transaction, you can see that only one application (Printr) has access to the user’s credentials, but he is willingly sharing some part of his data with chosen applications, which is a plus for security, apart from reducing redundancy like having separate copies of the same photo with different applications and also managing all the separate user accounts and passwords.

In this tutorial, we shall create a program that prints the user’s Google profile information. Most other OAuth providers processes will be similar, and are usually well documented on their developer pages.

Step 1: Obtain Client ID and Client Secret codes for your application.

1. Go to https://code.google.com/apis/console. If you don’t have a project defined until now, you will see a screen asking you to start using the Google APIs.

2. Create a project by either choosing "Add Project" in the previous screen or "Create …" from the drop down menu.

3. The default name for your first project is "API Project". You can rename it if you wish, but the name does not matter for us, and for now we can just leave it as it is.
4. For your new project, there is a "Services" table that should have no entries. Leave it as it is. We are not going to access any of these services for our example.
5. We also won’t make any changes in the Team tab for our example.
6. In the "API Access" tab, click on the big blue "Create an OAuth 2.0 Client ID" button and follow the wizard with the default values until the end.

7. Your API Access screen should now look something like this.

8. Click "API Access"->"Client ID for web applications"->"Edit Settings". Update the entry in the window as shown below. Notice that I have changed it from the default "https" to "http".

At this point, you have a Client ID code and a Client Secret code which you have to use in your application. You also need to use the Redirect URI - this is the URI to which the Google server redirects the page to return your OAuth code.

Step 2: OAuth Library Installation

To understand how to work with goinstall please go through the other tutorial at go packages and go install. Here I shall just repeat the gist of the more detailed information on installing an external API from this other tutorial using external api in a go web program.

* Setup the environment variable GOPATH to point to a directory of your choice (if you haven’t done so already).
The external source code will be downloaded into this directory $GOPATH/src and the packages also will be installed under this directory $GOPATH/pkg.
* Install the oauth2 go apis by entering the command
goinstall goauth2.googlecode.com/hg/oauth

Step 3: The code.

Since the program is fairly long, I’ve included the comments along with the code. I’ve also removed all error checks and other code to make it small, which of course is not recommended. To get a gist of the program, here is the pseudocode.

1. Create an instance of oauth.Config with
    a. your project’s Client ID text,
    b. project’s Client Secret text,
    c. the predefined authorization URL and …
    d. token URL,
    e. URL for the data scope you want access to,
    f. and the callback URL that Google should return the code to
2. Direct the user to Google Authentication page using AuthCodeURL()
3. When Google returns the authorization code to your URL, save the code.
4. Exchange the code for a token using a oauth.Transport type
5. Use the Transport which has the token to get the user details.

Partial program - your project’s client id and client secret has to be filled in
package main


import (
    "code.google.com/p/goauth2/oauth"
    "net/http"
    "html/template"
)

var notAuthenticatedTemplate = template.Must(template.New("").Parse(`
<html><body>
You have currently not given permissions to access your data. Please authenticate this app with the Google OAuth provider.
<form action="/authorize" method="POST"><input type="submit" value="Ok, authorize this app with my id"/></form>
</body></html>
`));

var userInfoTemplate = template.Must(template.New("").Parse(`
<html><body>
This app is now authenticated to access your Google user info.  Your details are:<br />
{{.}}
</body></html>
`));

// variables used during oauth protocol flow of authentication
var (
    code = ""
    token = ""
)

var oauthCfg = &oauth.Config {
        //TODO: put your project's Client Id here.  To be got from https://code.google.com/apis/console
        ClientId: "",

        //TODO: put your project's Client Secret value here https://code.google.com/apis/console
        ClientSecret: "",

        //For Google's oauth2 authentication, use this defined URL
        AuthURL: "https://accounts.google.com/o/oauth2/auth",

        //For Google's oauth2 authentication, use this defined URL
        TokenURL: "https://accounts.google.com/o/oauth2/token",

        //To return your oauth2 code, Google will redirect the browser to this page that you have defined
        //TODO: This exact URL should also be added in your Google API console for this project within "API Access"->"Redirect URIs"
        RedirectURL: "http://localhost:8080/oauth2callback",

        //This is the 'scope' of the data that you are asking the user's permission to access. For getting user's info, this is the url that Google has defined.
        Scope: "https://www.googleapis.com/auth/userinfo.profile",
    }

//This is the URL that Google has defined so that an authenticated application may obtain the user's info in json format
const profileInfoURL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"

func main() {
    http.HandleFunc("/", handleRoot)
    http.HandleFunc("/authorize", handleAuthorize)

    //Google will redirect to this page to return your code, so handle it appropriately
    http.HandleFunc("/oauth2callback", handleOAuth2Callback)

    http.ListenAndServe("localhost:8080", nil)
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
    notAuthenticatedTemplate.Execute(w, nil)
}

// Start the authorization process
func handleAuthorize(w http.ResponseWriter, r *http.Request) {
    //Get the Google URL which shows the Authentication page to the user
    url := oauthCfg.AuthCodeURL("")

    //redirect user to that page
    http.Redirect(w, r, url, http.StatusFound)
}

// Function that handles the callback from the Google server
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
    //Get the code from the response
    code := r.FormValue("code")

    t := &oauth.Transport{oauth.Config: oauthCfg}

    // Exchange the received code for a token
    t.Exchange(code)

    //now get user data based on the Transport which has the token
    resp, _ := t.Client().Get(profileInfoURL)

    buf := make([]byte, 1024)
    resp.Body.Read(buf)
    userInfoTemplate.Execute(w, string(buf))
}


Step 4: Compiling, Linking, Executing

My program file is called goauthweb.go and the oauth package I installed is at $GOPATH/pkg/linux_amd64. With that I compiled, linked, and executed the program as below on my 64bit Ubuntu machine.
* Compiling: 6g -I $GOPATH/pkg/linux_amd64 goauthweb.go
* Linking: 6l -L $GOPATH/pkg/linux_amd64 goauthweb.6
* Executing: ./6.out
* You can now test out the program at http://localhost:8080

You should be able to use a very similar procedure to make OAuth2 work in the AppEngine. For related information on the minor changes to be followed for the AppEngine for Go, please see my other tutorial at Using an external api in a Go AppEngine program

15 comments:

  1. I tried to do the same thing with Facebook, which also supports OAuth2, but got internal server errors on callback. Any tips?

    ReplyDelete
  2. I tried it too but getting an error at transport.Exchange. There seems to be an issue already raised at http://code.google.com/p/goauth2/issues/detail?id=4

    ReplyDelete
  3. Thanks for the tutorial. Somethings are changed after this tutorial has been published. These are few edits
    To install oauth: go get code.google.com/p/goauth2/oauth
    Changes in import:
    import (
    "code.google.com/p/goauth2/oauth"
    "net/http"
    "html/template"
    )

    ReplyDelete
  4. Thank you Nagasai, I've updated that.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hi Sathish,

    This seemingly breaks under Go 1.1 - when Google attempts to pass the token to the callback URL, the app panics.

    I've seemingly had a similar issue with another OAuth2 library—where it panics on return—which leads me to believe it's 1.1 causing the problem.

    ReplyDelete
  7. Also check out our Gomniauth package, which solves the same issues but can be adapted to support OAuth, LDAP and any other authentication service. https://github.com/stretchr/gomniauth

    ReplyDelete
  8. Just to let you know this location has changed again making this out of date "code.google.com/p/goauth2/oauth"

    ReplyDelete
  9. Do not, do not ever think that wearing glasses looked worried intellectuals bottle or something, looks handsome, looks pretty girls. Trap there, so hard to wear glasses, do not wear not understand!
    facebook entrar , entrar no facebook , entrar facebook , entrar facebook direto

    ReplyDelete
  10. Thanks for your post! I believe there are many who feel the same satisfaction as I read this article! I hope you will continue to have such articles to share with everyone!
    abcya

    ReplyDelete
  11. Thanks for your article! I have been looking for quite a long time and fortunately I read this article! I wish you would continue to have valuable articles like this or more to share with everyone!
    slither io

    ReplyDelete
  12. We are really grateful for your blog post. You will find a lot of approaches after visiting your post. Great work. Friv1 Friv 2 Friv20


    ReplyDelete
  13. OAuth is a service which is very effective and especially in putting forth security measures. I have learned a lot from the analysis which the author has shared. This is a very useful forum for the computer technicians.Edit my Law School Personal Statement

    ReplyDelete
  14. I was very impressed by this post, this site has always been pleasant news. Thank you very much for such an interesting post. Keep working, great job! In my free time, I like play game: mutilateadoll2game.com. What about you?

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.