| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- 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)
- }
|