1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
package httpauth
import (
"bytes"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"strings"
)
type basicAuth struct {
h http.Handler
opts AuthOptions
}
// AuthOptions stores the configuration for HTTP Basic Authentication.
//
// A http.Handler may also be passed to UnauthorizedHandler to override the
// default error handler if you wish to serve a custom template/response.
type AuthOptions struct {
Realm string
User string
Password string
AuthFunc func(string, string, *http.Request) bool
UnauthorizedHandler http.Handler
}
// Satisfies the http.Handler interface for basicAuth.
func (b basicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check if we have a user-provided error handler, else set a default
if b.opts.UnauthorizedHandler == nil {
b.opts.UnauthorizedHandler = http.HandlerFunc(defaultUnauthorizedHandler)
}
// Check that the provided details match
if b.authenticate(r) == false {
b.requestAuth(w, r)
return
}
// Call the next handler on success.
b.h.ServeHTTP(w, r)
}
// authenticate retrieves and then validates the user:password combination provided in
// the request header. Returns 'false' if the user has not successfully authenticated.
func (b *basicAuth) authenticate(r *http.Request) bool {
const basicScheme string = "Basic "
if r == nil {
return false
}
// In simple mode, prevent authentication with empty credentials if User is
// not set. Allow empty passwords to support non-password use-cases.
if b.opts.AuthFunc == nil && b.opts.User == "" {
return false
}
// Confirm the request is sending Basic Authentication credentials.
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, basicScheme) {
return false
}
// Get the plain-text username and password from the request.
// The first six characters are skipped - e.g. "Basic ".
str, err := base64.StdEncoding.DecodeString(auth[len(basicScheme):])
if err != nil {
return false
}
// Split on the first ":" character only, with any subsequent colons assumed to be part
// of the password. Note that the RFC2617 standard does not place any limitations on
// allowable characters in the password.
creds := bytes.SplitN(str, []byte(":"), 2)
if len(creds) != 2 {
return false
}
givenUser := string(creds[0])
givenPass := string(creds[1])
// Default to Simple mode if no AuthFunc is defined.
if b.opts.AuthFunc == nil {
b.opts.AuthFunc = b.simpleBasicAuthFunc
}
return b.opts.AuthFunc(givenUser, givenPass, r)
}
// simpleBasicAuthFunc authenticates the supplied username and password against
// the User and Password set in the Options struct.
func (b *basicAuth) simpleBasicAuthFunc(user, pass string, r *http.Request) bool {
// Equalize lengths of supplied and required credentials
// by hashing them
givenUser := sha256.Sum256([]byte(user))
givenPass := sha256.Sum256([]byte(pass))
requiredUser := sha256.Sum256([]byte(b.opts.User))
requiredPass := sha256.Sum256([]byte(b.opts.Password))
// Compare the supplied credentials to those set in our options
if subtle.ConstantTimeCompare(givenUser[:], requiredUser[:]) == 1 &&
subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1 {
return true
}
return false
}
// Require authentication, and serve our error handler otherwise.
func (b *basicAuth) requestAuth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, b.opts.Realm))
b.opts.UnauthorizedHandler.ServeHTTP(w, r)
}
// defaultUnauthorizedHandler provides a default HTTP 401 Unauthorized response.
func defaultUnauthorizedHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
// BasicAuth provides HTTP middleware for protecting URIs with HTTP Basic Authentication
// as per RFC 2617. The server authenticates a user:password combination provided in the
// "Authorization" HTTP header.
//
// Example:
//
// package main
//
// import(
// "net/http"
// "github.com/zenazn/goji"
// "github.com/goji/httpauth"
// )
//
// func main() {
// basicOpts := httpauth.AuthOptions{
// Realm: "Restricted",
// User: "Dave",
// Password: "ClearText",
// }
//
// goji.Use(httpauth.BasicAuth(basicOpts), SomeOtherMiddleware)
// goji.Get("/thing", myHandler)
// }
//
// Note: HTTP Basic Authentication credentials are sent in plain text, and therefore it does
// not make for a wholly secure authentication mechanism. You should serve your content over
// HTTPS to mitigate this, noting that "Basic Authentication" is meant to be just that: basic!
func BasicAuth(o AuthOptions) func(http.Handler) http.Handler {
fn := func(h http.Handler) http.Handler {
return basicAuth{h, o}
}
return fn
}
// SimpleBasicAuth is a convenience wrapper around BasicAuth. It takes a user and password, and
// returns a pre-configured BasicAuth handler using the "Restricted" realm and a default 401 handler.
//
// Example:
//
// package main
//
// import(
// "net/http"
// "github.com/zenazn/goji/web/httpauth"
// )
//
// func main() {
//
// goji.Use(httpauth.SimpleBasicAuth("dave", "somepassword"), SomeOtherMiddleware)
// goji.Get("/thing", myHandler)
// }
//
func SimpleBasicAuth(user, password string) func(http.Handler) http.Handler {
opts := AuthOptions{
Realm: "Restricted",
User: user,
Password: password,
}
return BasicAuth(opts)
}
|