abbot / go-http-auth Goto Github PK
View Code? Open in Web Editor NEWBasic and Digest HTTP Authentication for golang http
License: Apache License 2.0
Basic and Digest HTTP Authentication for golang http
License: Apache License 2.0
It seems the API is stable enough to tag the v1 of the API. Please do it for the better maintainability of the code.
See here: http://labix.org/gopkg.in
Thanks!
[Scene]
hi all
we use the go-http-auth for request authrization, but it takes up high CPU usage, the CPU graph as follows:
[Code analysis]
According to the code, we see there is 1000 times loop for computing "final", we want to know if the operation is in line with expectations.
The questions are as follows:
Thanks a lot !
The response data is hard code as "401 Unauthorized" in RequireAuth(), is there a way to write custom response data?
Could you add a function like below?
func (a *DigestAuth) CustomRequireAuth(w http.ResponseWriter, r *http.Request, data []byte) {
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
a.Purge(a.ClientCacheTolerance * 2)
}
nonce := RandomKey()
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
w.Header().Set("WWW-Authenticate",
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
a.Realm, nonce, a.Opaque))
w.WriteHeader(401)
w.Write(data)
}
I'm trying to test the code, when I using digest authorization, it will repeat asking password or each different page, any idea?
b6a92f4 does not pass unit tests with Go 1.10. At least:
+ GOPATH=/builddir/build/BUILD/go-http-auth-b6a92f468f2b01961e796d20e3ec5bbb8a8297d6/_build:/usr/share/gocode
+ go test -buildmode pie -compiler gc -ldflags '-extldflags '\''-Wl,-z,relro '\'''
# github.com/abbot/go-http-auth
./misc_test.go:35: Fatalf format %s reads arg #3, but call has only 2 args
FAIL github.com/abbot/go-http-auth [build failed]
Is there any mechanism for session expiry management here already?
Hi,
I'm evaluating this lib to use it in our proyect. However, we have a distributed environment and we can't share these values across the nodes. Maybe a plugable backend like postgres would be a nice idea. What do you think?
PD: Thanks for your great work
This library is great, and seems to be a full replacement for the standard Apache http auth. However it is impossible to actually use if someone doesn't know how to set passwords. I think people are left doing what I am doing: scratching my head and reading the source code, guessing, and failing repeatedly.
Ultimately this needs a simple extension to the README that:
Ideally one example shows how to just make it read a standard .htaccess
file and have it just work. Everything else is a corner case.
If I figure this out, I'll make a PR.
As the methodes like NewBasicAuthenticator etc. are deprecated, is there an updated of example of how to use this package?
Hi,
This library is something I want to use, but there are no examples for how to use it. Specifically, your README does not show how to implement a Secret() func.. the comment says that the password is "hello", but it returns some hash. How should I generate that hash?
Thanks,
Rob
From https://www.ietf.org/rfc/rfc2617.txt
The Authorization header may be included
preemptively; doing so improves server efficiency and avoids extra
round trips for authentication challenges. The server may choose to
accept the old Authorization header information, even though the
nonce value included might not be fresh. Alternatively, the server
may return a 401 response with a new nonce value, causing the client
to retry the request; by specifying stale=TRUE with this response,
the server tells the client to retry with the new nonce, but without
prompting for a new username and password.
Currently CheckAuth just returns if it doesn't find the nonce in its cache. Would be nice to support stale=TRUE.
I will send a PR
I'm finding this works great on most of my sites, and has been a drop-in replacement for Apache, but I have some exceptions. On a page that embeds two iframes that hit my protected (ie hits the server to check passwords in parallel twice at nearly the same moment), even if I have the password cached already, it fails to find a password, and gets int a loop where no matter how many times I enter it, it fails.
Curious if there is a race condition behind this. I'm attempting to debug.
Please consider to assign version numbers and tag releases.
Tags/releases are useful for downstream package maintainers (in Debian and other distributions) to export source tarballs, automatically track new releases and to declare dependencies between packages.
Read more in the Debian Upstream Guide.
Thank you.
can you add "plain" passwords file support for htpass files?
There are two implementations for that one.
The nginx version which uses ":{PLAIN}" as a separator between the user and the password.
The apache version which uses the first ":" as a separator between the user and the password.
I have seen examples of it at:
http://aspirine.org/htpasswd_en.html
With around 100 parallel clients there is a crash observed. Similar to what was reported in #62
Stack trace -
fatal error: concurrent map read and map write
goroutine 16622 [running]:
runtime.throw(0xb0c9fd, 0x21)
/usr/local/go/src/runtime/panic.go:774 +0x72 fp=0xc0005ad7f0 sp=0xc0005ad7c0 pc=0x42f5d2
runtime.mapaccess2_faststr(0xa1ef60, 0xc0002a20c0, 0xc000275a28, 0x10, 0xc0006e0098, 0x0)
/usr/local/go/src/runtime/map_faststr.go:116 +0x48f fp=0xc0005ad860 sp=0xc0005ad7f0 pc=0x4144ff
github.com/abbot/go-http-auth.(*DigestAuth).CheckAuth(0xc000252120, 0xc0005d8100, 0x0, 0x0, 0x0)
/root/go/src/edmservice/pkg/mod/github.com/abbot/[email protected]/digest.go:185 +0x847 fp=0xc0005adaf8 sp=0xc0005ad860 pc=0x9822a7
github.com/abbot/go-http-auth.(*DigestAuth).Wrap.func1(0xbe0420, 0xc00028c000, 0xc0005d8100)
/root/go/src/edmservice/pkg/mod/github.com/abbot/[email protected]/digest.go:219 +0x57 fp=0xc0005adb90 sp=0xc0005adaf8 pc=0x9841a7
net/http.HandlerFunc.ServeHTTP(0xc000254060, 0xbe0420, 0xc00028c000, 0xc0005d8100)
/usr/local/go/src/net/http/server.go:2007 +0x44 fp=0xc0005adbb8 sp=0xc0005adb90 pc=0x7cf504
net/http.(*ServeMux).ServeHTTP(0x1050020, 0xbe0420, 0xc00028c000, 0xc0005d8100)
/usr/local/go/src/net/http/server.go:2387 +0x1bd fp=0xc0005adc18 sp=0xc0005adbb8 pc=0x7d13dd
net/http.serverHandler.ServeHTTP(0xc0002ca000, 0xbe0420, 0xc00028c000, 0xc0005d8100)
/usr/local/go/src/net/http/server.go:2802 +0xa4 fp=0xc0005adc48 sp=0xc0005adc18 pc=0x7d2954
net/http.(*conn).serve(0xc000520140, 0xbe1a20, 0xc00075a940)
/usr/local/go/src/net/http/server.go:1890 +0x875 fp=0xc0005adfc8 sp=0xc0005adc48 pc=0x7ce2f5
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1357 +0x1 fp=0xc0005adfd0 sp=0xc0005adfc8 pc=0x45bb91
created by net/http.(*Server).Serve
/usr/local/go/src/net/http/server.go:2927 +0x38e
The code for basic authentication says that for user "john" and password "hello" the secret is "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1". How is this 'secret' generated? Is there a command line utilty to do this?
At some point a mutex was added to mediate access to digest.clients but it was removed in subsequent commits for some reason. Rather than relying on correct use of a mutex, why not use a sync.Map?
When a server instance is hit with a sufficient number of concurrent requests (client code below) concurrent write to digest.clients results in a panic.
package main
import (
"fmt"
"net/http"
"sync"
"time"
"github.com/bobziuchkovski/digest"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(80)
startTime := time.Now()
for i := 0; i < 80; i++ {
go func() {
defer wg.Done()
t := digest.NewTransport("admin", "admin")
req, err := http.NewRequest("GET", "http://127.0.0.1", nil)
if err != nil {
fmt.Println(err)
return
}
_, err = t.RoundTrip(req)
if err != nil {
fmt.Println(err)
return
}
}()
}
wg.Wait()
fmt.Printf("time taken: %v", time.Now().Sub(startTime).String())
}
The situation is following:
config.yaml
is structured in the following way:auth:
- /path1:
- username1: <username>
password1: <password>
- username2: <username>
password2: <password>
- /path2:
- username: <username>
password: <password>
Now during the app initialization, I would like to enable Basic authentication for the path if respective path record is present and disable it otherwise (i.e. let all incoming request pass). I didn't manage to do that in any way, because auth expects auth.AuthenticatedHandlerFunc
while http expects http.HandlerFunc
and they are not interchangeable. That means that you need 2 handler functions - one for normal http, the other for Basic auth. That is kinda cumbersome.
So I just wanted to ask if I didn't oversee something. Am I right that it cannot be done easily as the interface is design right now?
Perhaps there could be another version of auth.Wrap
that would simply put an empty Username into the AuthenticatedRequest
passed into the handler and would not even attempt to authenticate the request?
Let me know what you think about it, I could even write it myself and send a pull request!
I've created htpasswd files using the htpasswd utility and, while go-http-auth supports the file format, the hashed passwords aren't matching up.
This isn't my area of expertise, but I suspect go-http-auth is using a different salt than htpasswd and perhaps there are some other minor differences in how the MD5 and SHA algorithms are used between htpasswd and go-http-auth. Or I am I missing something?
If htpasswd encrypted passwords aren't going to be supported by go-http-auth, how about a similar utility that will create an httpasswd file that will work with go-http-auth.
Very happy with the package, by the way.
Thanks.
Hello. I have ran into a problem where in digest.go#L262 da.Purge
is called while da.mutex
is locked, with da.Purge
itself trying to acquire lock on it.
This causes double lock and all further digest authentication requests hang until they time out.
This can be fixed either by removing lines 74, 75, 92 and 99, so that da.Purge
is always called when da.mutex
is locked but is not trying to acquire lock itself, or by adding da.mutex.Unlock()
and da.mutex.Lock()
around line 262 so that da.Purge
is only called when da.mutex
is not locked.
Hey hey,
a bit of time passed by since the last release was made. Please add a new one since master branch contains a few important bug fixes. The latest release 0.3.0 isn't working for me, master is. :)
Thanks in advance and 👋 from Germany!
import (
"github.com/abbot/go-http-auth"
...should be...
import (
auth "github.com/abbot/go-http-auth"
i.e. Add "auth" and the example becomes a working example.
A logout/rerequest authentication after XX seconds of inactivity
How is this hash created?
$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1
Hey,
a bit of time passed by since the last release was made. Please add a new one since master branch contains a few important bug fixes. The latest release 0.4.0 isn't working for me, master is. :)
You should not use constant time comparisons (due to possible timing attacks), and is the hashing done using md5 by default? See https://github.com/goji/httpauth for how it does this.
digest.go
algorithm=MD5 , not algorithm="MD5"
Hi,
thanks for your great work, it's very usefull for me.
I did a little change to add support for more crypters:
Do you want a pull request for that?
Thanks and Kind regards,
René
It good that we may set basic auth on per-endpoint level
http.HandleFunc("/user/", authenticator.Wrap(handleUser))
http.HandleFunc("/book/", authenticator.Wrap(handleBook))
But for most cases it would be better to have some global "interceptor" for all endpoints.
And looks like the most simple way is to make a delegating Handler and use it with SericeMux.
Here I created a basic sample:
https://gist.github.com/stokito/24b3e6efb3e1e9357c7d964a7df2c18c
Can you add such AuthHandler into the library?
I would like to use go-http-auth
to authenticate users with a LDAP server.
I'm able to do it with the following code but I would like to avoid having to copy code from go-http-auth
(code between lines comments).
package main
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
"github.com/abbot/go-http-auth"
)
type myBasicAuth struct {
base auth.BasicAuth
}
func (a *myBasicAuth) CheckAuth(r *http.Request) string {
// --------------------------------------
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 || s[0] != "Basic" {
return ""
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return ""
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return ""
}
user, password := pair[0], pair[1]
// --------------------------------------
fmt.Printf("user: %s, password: %s\n", user, password)
// ** ldap code here **
return ""
}
func (a *myBasicAuth) Wrap(wrapped auth.AuthenticatedHandlerFunc) http.HandlerFunc {
// --------------------------------------
return func(w http.ResponseWriter, r *http.Request) {
if username := a.CheckAuth(r); username == "" {
a.base.RequireAuth(w, r)
} else {
ar := &auth.AuthenticatedRequest{Request: *r, Username: username}
wrapped(w, ar)
}
}
// --------------------------------------
}
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
}
func main() {
authenticator := &myBasicAuth{auth.BasicAuth{Realm: "example.com"}}
http.HandleFunc("/", authenticator.Wrap(handle))
http.ListenAndServe("127.0.0.1:8080", nil)
}
Hi,
According to spec https://tools.ietf.org/html/rfc2617 I don't see a Header value in the Authorization Request, but in digest.go you are checking this value. Is it right?
I'm using Postman and I can't send a request to my app :-(. There is a related issue about this in Postman here.
say I have a password 'cracked' in plain text ; how would I convert it into a hash ?
The digest module CheckAuth function makes the reasonable assumption that the client-sent nonce-count ("nc") always increases. Unfortunately, in practice, because a client can have multiple connections to a server, some possibly through proxies and/or via AJAX, client-sent nonce-counts don't always increase from the POV of the server. Strictly the requirement is that a nonce-count is not reused, not that it always increases. I am observing this as I try to use go-http-auth in a real world setting. I will send a PR to either track nonces with a bitmap or add an option to turn off nc checking. Thank you for open sourcing this excellent lib.
References:
https://lists.w3.org/Archives/Public/ietf-http-wg/2003JulSep/0006.html
https://code.google.com/p/chromium/issues/detail?id=37526
RFC 7616 defines a new algorithm header field and specifies SHA-256 and SHA-512/256 as valid options. It still supports MD5 for backwards compatibility, but explicitly recommends against it.
I realize that the hash being returned on line 22 in digest.go is simply the md5 of "john:example.com:hello". So I tried creating a new hash with "bruda:example.com:hello" , changed "john" to "bruda" on line 20, and replaced the hash on line 22. But now the authentication fails & continues to ask for login info. I double checked the original hash with the same md5 tool and it matched up with "john:example.com:hello" as expected. What am I missing here?
Could you please provide example code how ine would use this package to produce a HTTP GET request against a Digest authenticated server?
The code as currently written only detects the bcrypt prefix "$2y$"; it should also handle "$2a$" and "$2b$". Please see https://en.wikipedia.org/wiki/Bcrypt.
Would you be able to add support for bcrypt, as this is now available from htpasswd?
how to encode password "hello" to "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" ?
go build -race, will add instrumentation to catch race conditions.
It caught problem within DigestAuth.clients (map)
I'm attaching a copy of digest.go.
I changed the mutex to be a read/write mutex, so you could have finer grain control over it.
this copy doesn't cause problems with -race
Also I started to fix golint problems https://github.com/golang/lint. But stopped, because it was getting out of hand. (Sorry for dirtying up the answer)
digest.txt
Thanks for your awesome work on the repo!!
digest.txt is really digest.go!
Hi there,
I'm running into a problem because using the library essentially changes the signature of the handle func and requires AuthenticatedRequest everywhere. This makes all subsequent method calls to methods that require http.Request fail.
Proxy authentication is nearly identical to origin server authentication except for a few details like header names. Would be nice if this lib could be used in a proxy server implementation. I'm working on this and will submit a PR.
URIs such as "css?family=Source+Sans+Pro:400,700,4" are not parsed correctly due to the embedded commas. In this case, result["uri"] ends up being "/css?family=Source+Sans+Pro:400", subsequently the digest computed in CheckAuth does not match the response in the Proxy-Authorization header.
GET http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro HTTP/1.1^M
Host: fonts.googleapis.com^M
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:43.0) Gecko/20100101 Firefox/43.0^M
Accept: text/css,/;q=0.1^M
Accept-Language: en-US,en;q=0.5^M
Accept-Encoding: gzip, deflate^M
Referer: http://elm-lang.org/assets/style.css^M
Proxy-Authorization: Digest username="test", realm="", nonce="FRPnGdb8lvM1UHhi", uri="/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro", algorithm=MD5, response="fdcdd78e5b306ffed343d0ec3967f2e5", opaque="lEgVjogmIar2fg/t", qop=auth, nc=00000001, cnonce="e76b05db27a3b323"^M
Connection: keep-alive^M
A quick Google indicates this is a common issue in digest code, see
https://bz.apache.org/bugzilla/show_bug.cgi?id=37132
rails/rails#5485
https://twistedmatrix.com/trac/ticket/6445
I will send a PR to fix
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.