Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way of requiring HTTP Basic Auth in Go?

Tags:

go

Situation:

I'm building a REST API using Gorilla's mux as the router.

I'm wondering how I can protect specific routes with simple HTTP Basic Auth. I don't have a need to read the credentials from a file or any external source, I really just want to protect selected routes by a hard coded HTTP Basic Auth username and password.

Question:

What is the idiomatic way of doing so in Go? Does Gorilla offer anything to make it more easy? If you could provide a few lines of code, that would be just wonderful.

like image 370
Ralf Avatar asked Feb 21 '14 13:02

Ralf


People also ask

How safe is HTTP basic-auth?

Note: The HTTP basic authentication scheme can be considered secure only when the connection between the web client and the server is secure. If the connection is insecure, the scheme does not provide sufficient security to prevent unauthorized users from discovering the authentication information for a server.

How can I pass the basic HTTP authentication or token authentication?

It is indeed not possible to pass the username and password via query parameters in standard HTTP auth. Instead, you use a special URL format, like this: http://username:[email protected]/ -- this sends the credentials in the standard HTTP "Authorization" header.


2 Answers

Combining a couple of answers into an easy copy/paste:

// BasicAuth wraps a handler requiring HTTP basic auth for it using the given // username and password and the specified realm, which shouldn't contain quotes. // // Most web browser display a dialog with something like: // //    The website says: "<realm>" // // Which is really stupid so you may want to set the realm to a message rather than // an actual realm. func BasicAuth(handler http.HandlerFunc, username, password, realm string) http.HandlerFunc {      return func(w http.ResponseWriter, r *http.Request) {          user, pass, ok := r.BasicAuth()          if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {             w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)             w.WriteHeader(401)             w.Write([]byte("Unauthorised.\n"))             return         }          handler(w, r)     } }  ...  http.HandleFunc("/", BasicAuth(handleIndex, "admin", "123456", "Please enter your username and password for this site")) 

Note that subtle.ConstantTimeCompare() still depends on the length, so it is probably possible for attackers to work out the length of the username and password if you do it like this. To get around that you could hash them or add a fixed delay.

like image 140
Timmmm Avatar answered Sep 19 '22 23:09

Timmmm


Check req.BasicAuth() https://golang.org/pkg/net/http/#Request.BasicAuth

You can check this in your handler or wrap your handler like so:

func auth(fn http.HandlerFunc) http.HandlerFunc {   return func(w http.ResponseWriter, r *http.Request) {     user, pass, _ := r.BasicAuth()     if !check(user, pass) {        http.Error(w, "Unauthorized.", 401)        return     }     fn(w, r)   } } 

Where

check(u, p string) bool  

is a function you will have to write yourself based on how you are storing credentials. Now you can use:

auth(originalHandler) 

Wherever you were passing originalHandler before.

[edit: It's worth adding that your check function should be resistant to side channel attacks like timing attacks. Also stored passwords should be hashed with a cryptographically random salt. Also you should probably use OAuth instead and let an established identity provider worry about password security for you.]

like image 30
voutasaurus Avatar answered Sep 20 '22 23:09

voutasaurus