Skip to content

Instantly share code, notes, and snippets.

@kisielk
Created October 4, 2012 05:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kisielk/3831610 to your computer and use it in GitHub Desktop.
Save kisielk/3831610 to your computer and use it in GitHub Desktop.
An example of Google Persona login in Go
// An example of implementing Mozilla's Persona authentication system.
// follows the instructions from https://developer.mozilla.org/en-US/docs/Persona/Quick_Setup
// and doesn't do much else.
package main
import (
"encoding/json"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"html/template"
"log"
"net/http"
"net/url"
)
// The main page template.
var tmpl = `
<html>
<head>
<title>Persona Test</title>
<script src="https://login.persona.org/include.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
</head>
<body>
<div>
{{if .Email}}
Signed in as {{.Email}}: <span id="signout"><a href="#">Sign out</a></span>
{{else}}
Not signed in: <span id="signin"><a href="#">Sign in</a></span>
{{end}}
<script>
var signinLink = document.getElementById('signin');
if (signinLink) {
signinLink.onclick = function() { navigator.id.request(); };
};
var signoutLink = document.getElementById('signout');
if (signoutLink) {
signoutLink.onclick = function() { navigator.id.logout(); };
};
{{if .Email}}
var currentUser = {{.Email}};
{{else}}
var currentUser = null;
{{end}}
navigator.id.watch({
loggedInUser: currentUser,
onlogin: function(assertion) {
// A user has logged in! Here you need to:
// 1. Send the assertion to your backend for verification and to create a session.
// 2. Update your UI.
$.ajax({ /* <-- This example uses jQuery, but you can use whatever you'd like */
type: 'POST',
url: '/auth/login', // This is a URL on your website.
data: {assertion: assertion},
success: function(res, status, xhr) { window.location.reload(); },
error: function(res, status, xhr) { alert("login failure" + res); }
});
},
onlogout: function() {
// A user has logged out! Here you need to:
// Tear down the user's session by redirecting the user or making a call to your backend.
// Also, make that loggedInUser will get set to null on the next page load.
// (That's a literal JavaScript null. Not false, 0, or undefined. null.)
$.ajax({
type: 'POST',
url: '/auth/logout', // This is a URL on your website.
success: function(res, status, xhr) { window.location.reload(); },
error: function(res, status, xhr) { alert("logout failure" + res); }
});
}
});
</script>
</body>
</html>
`
// the audience string as required by the Persona API. Should be adjusted as appropriate.
var audience = "http://localhost:8080"
// a session cookie store
var sessionStore = sessions.NewCookieStore([]byte("something-very-secret"))
// the name of the session cookie. Should be adjusted as appropriate
var sessionName = "persona"
// serverError is a helper function for printing server error messages
// note that we're printing error messages directly to the client,
// this is probably not a good idea in production.
func serverError(w http.ResponseWriter, s string) {
log.Println("server error:", s)
http.Error(w, "server error: "+s, http.StatusInternalServerError)
}
// badRequest returns a bad request status to the client with a message
func badRequest(w http.ResponseWriter, s string) {
http.Error(w, s, http.StatusBadRequest)
}
// handleLogin is the handler for /auth/login
func handleLogin(w http.ResponseWriter, req *http.Request) {
assertion := req.FormValue("assertion")
if assertion == "" {
badRequest(w, "missing assertion")
return
}
resp, err := http.PostForm("https://verifier.login.persona.org/verify",
url.Values{"assertion": {assertion}, "audience": {audience}})
if err != nil {
serverError(w, err.Error())
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
var response struct {
Status string `json:"status"`
Email string `json:"email"`
Audience string `json:"audience"`
Expires uint64 `json:"expires"`
Issuer string `json:"issuer"`
Reason string `json:"reason"` // Will only be set if Status != "okay"
}
if err := dec.Decode(&response); err != nil {
serverError(w, err.Error())
}
if response.Status != "okay" {
serverError(w, "authentication failed: "+response.Reason)
}
session, err := sessionStore.Get(req, sessionName)
if err != nil {
serverError(w, err.Error())
}
session.Values["email"] = response.Email
if err := session.Save(req, w); err != nil {
serverError(w, err.Error())
}
}
// handleLogout is the handler for /auth/logout
func handleLogout(w http.ResponseWriter, req *http.Request) {
session, err := sessionStore.Get(req, sessionName)
if err != nil {
serverError(w, err.Error())
}
delete(session.Values, "email")
if err := session.Save(req, w); err != nil {
serverError(w, err.Error())
}
}
// handle is the main page handler
func handle(w http.ResponseWriter, req *http.Request) {
t, err := template.New("main").Parse(tmpl)
if err != nil {
serverError(w, err.Error())
return
}
session, err := sessionStore.Get(req, sessionName)
if err != nil {
serverError(w, err.Error())
}
err = t.ExecuteTemplate(w, "main", map[string]interface{}{"Email": session.Values["email"]})
if err != nil {
serverError(w, err.Error())
return
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handle)
r.HandleFunc("/auth/login", handleLogin).Methods("POST")
r.HandleFunc("/auth/logout", handleLogout).Methods("POST")
http.Handle("/", r)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment