|
package main |
|
|
|
import ( |
|
"net/http" |
|
"net/url" |
|
"flag" |
|
"log" |
|
"encoding/json" |
|
"io/ioutil" |
|
"strings" |
|
"encoding/base64" |
|
"fmt" |
|
"github.com/gorilla/mux" |
|
"github.com/astaxie/beego/session" |
|
) |
|
|
|
var ( |
|
// Register new app at https://auth0.com and provide |
|
// clientId (-id), clientSecret (-secret) and callbackURL (-redirect) |
|
// as imput arguments. |
|
domain = "contoso.auth0.com" |
|
clientId = "OGoZgeM7K56GflfNwWCwEIMg9kzQ5Ome" |
|
clientSecret = "tfK3imkasjtRFRR7ss-iRSX2BiclNWwNYqNdzQ85jIJzuQg2biSQw2WjShCOitT9" |
|
callbackURL = "http://localhost:8080/callback" |
|
) |
|
|
|
var sessionManager *session.Manager |
|
func init() { |
|
sessionManager, _ = session.NewManager("memory", `{"cookieName":"gosessionid","gclifetime":3600}`) |
|
go sessionManager.GC() |
|
} |
|
|
|
var router *mux.Router |
|
|
|
func main() { |
|
flag.Parse() |
|
|
|
router = mux.NewRouter() |
|
|
|
http.HandleFunc("/", router.ServeHTTP) |
|
|
|
router.HandleFunc("/", homeHandler).Methods("GET") |
|
router.HandleFunc("/callback", callbackHandler).Methods("GET") |
|
|
|
http.ListenAndServe(":8080", nil) |
|
} |
|
|
|
func homeHandler(w http.ResponseWriter, r *http.Request) { |
|
sess := sessionManager.SessionStart(w, r) |
|
defer sess.SessionRelease(w) |
|
profile, ok := sess.Get("profile").(map[string]interface{}) |
|
if ok && profile != nil { |
|
fmt.Fprintf(w, |
|
`<html> |
|
<body> |
|
<div>Welcome %v</div> |
|
<div><pre><code>%#v</code></pre></div> |
|
</body> |
|
</html>`, profile["name"], profile) |
|
} else { |
|
|
|
fmt.Fprintf(w, |
|
`<html> |
|
<body> |
|
<script src="https://cdn.auth0.com/w2/auth0-widget-5.2.min.js"></script> |
|
<script type="text/javascript"> |
|
|
|
var widget = new Auth0Widget({ |
|
domain: '%v', |
|
clientID: '%v', |
|
callbackURL: '%v' |
|
}); |
|
|
|
</script> |
|
<button onclick="widget.signin({ scope: 'openid profile' })">Login</button> |
|
</body> |
|
</html>`, domain, clientId, callbackURL) |
|
} |
|
} |
|
|
|
func callbackHandler(w http.ResponseWriter, r *http.Request) { |
|
code := r.URL.Query().Get("code") |
|
// state can be sent on the login request to keep some value across the redirect flow |
|
// for instance a return url. It's also used for CSRF |
|
// state := r.URL.Query().Get("state") |
|
|
|
token, err := ExchangeCode(code); |
|
if err != nil { |
|
fmt.Fprintf(w, |
|
`<html><body>Error: %v</body></html>`, err) |
|
return |
|
} |
|
|
|
id_token := token["id_token"].(string) |
|
parts := strings.Split(id_token, ".") |
|
var claimBytes []byte |
|
if claimBytes, err = DecodeSegment(parts[1]); err != nil { |
|
log.Fatalf("Error decoding token: %v", string(parts[1])) |
|
} |
|
|
|
var user map[string]interface{} |
|
if err = json.Unmarshal(claimBytes, &user); err != nil { |
|
log.Fatalf("Error parsing json: %v", string(claimBytes)) |
|
} |
|
|
|
// store profile and token in session |
|
sess := sessionManager.SessionStart(w, r) |
|
defer sess.SessionRelease(w) |
|
sess.Set("profile", user) |
|
sess.Set("id_token", token["access_token"]) |
|
sess.Set("access_token", token["id_token"]) |
|
|
|
// redirect to home (or get a return url from session or state) |
|
http.Redirect(w, r, "/", http.StatusMovedPermanently) |
|
} |
|
|
|
func ExchangeCode(code string) (map[string]interface{}, error) { |
|
params := url.Values{} |
|
client := &http.Client{} |
|
req, err := http.NewRequest("POST", "https://" + domain + "/oauth/token" , nil) |
|
if err != nil { return nil, err } |
|
|
|
req.Header.Set("Accept", "application/json") |
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
|
params.Set("grant_type", "authorization_code") |
|
params.Set("code", code) |
|
params.Set("client_id", clientId) |
|
params.Set("client_secret", clientSecret) |
|
params.Set("redirect_uri", callbackURL) |
|
|
|
encParams := params.Encode() |
|
reader := strings.NewReader(encParams) |
|
req.Body = ioutil.NopCloser(reader) |
|
req.ContentLength = int64(len(encParams)) |
|
|
|
resp, err := client.Do(req); |
|
if err != nil { return nil, err } |
|
raw, err := ioutil.ReadAll(resp.Body) |
|
defer resp.Body.Close() |
|
if err != nil { return nil, err } |
|
if resp.StatusCode != 200 { |
|
return nil, &AuthenticationError{err_code: "invalid_code", err_description: string(raw)} |
|
} |
|
|
|
var token map[string]interface{} |
|
if err := json.Unmarshal(raw, &token); err != nil { |
|
return nil, &AuthenticationError{err_code: "invalid_token", err_description: "unable to parse to json. raw: " + string(raw)} |
|
} |
|
|
|
return token, nil |
|
} |
|
|
|
type AuthenticationError struct { |
|
err_code string |
|
err_description string |
|
} |
|
|
|
func (e AuthenticationError) Error() string { |
|
return fmt.Sprintf("%v: %v", e.err_code, e.err_description) |
|
} |
|
|
|
func DecodeSegment(seg string) ([]byte, error) { |
|
if l := len(seg) % 4; l > 0 { |
|
seg += strings.Repeat("=", 4-l) |
|
} |
|
|
|
return base64.URLEncoding.DecodeString(seg) |
|
} |
🐦