Show HN: Keratin AuthN – Accounts and Auth Microservice in Go
keratin.tech
keratin.tech
> ORY Hydra is not an identity provider (user sign up, user log in, password reset flow), but connects to your existing identity provider through a consent app.
AuthN IS all the things that Dex and Hydra say they are not. I'll bet it could integrate with both given a bit of investment, e.g. by satisfying the "consent app" expectations.
AuthN does use as much of the OpenID Connect protocol as I could manage though. I started there and streamlined down to optimize for API-driven interactions rather than the redirect-driven interactions that are common with OAuth and OIC.
I've just started dabbling on a small project and would be interested to understand how features overlap and differences in license/distribution model.
Aside from being OSS, one major difference is that Keratin AuthN is purely an API. It's optimized for customization so that it will fit with any bespoke (secure) UX you want to provide. I found Auth0's API to be something of an after-thought, second to their hosted/branded/templatable pages.
"Dex is NOT a user-management system, but acts as a portal to other identity providers through "connectors." This lets dex defer authentication to LDAP servers, SAML providers, or established identity providers like GitHub, Google, and Active Directory."
It seems like AuthN IS a user management system. So that's a big difference right there.
With inbound federation that shouldn't be much of a problem, but with outbound federation you'll have some very difficult questions to answer (especially because all major identity solutions are pretty much OIC centric these days)
Adding support for inbound federation is on the roadmap. Support for outbound federation using OIC isn't out of the question either, but I don't yet see the motivation.
We evaluated Traefik and Kong. Decision was for Kong, since we need more features like auth, logging, rate limit.
Have you seen the /stats endpoint? It exposes the metrics as JSON, which may be a good match for your suggestion. I'd also like to export the key events to a STATSD-compatible sink so a sophisticated user can manage metrics in their own system.
Redis is already on hand because of other features though, and HLL is a pretty cheap integration. I figure it's a decent starting point for many people.
Being tied to Redis is also not great. For instance, it looks like everything apart from the metrics could comfortably live in Google Cloud Storage if you had a backend for it. Cloud Storage really isn't an appropriate place to put frequently updated counters, though. So if I deployed AuthN, I would want to be able to ditch Redis.
I should stress, though, that these are quibbles. I think AuthN looks very nice, and the quibbles are cropping up because I'm trying to figure out if I can use it. :)
* Google Cloud Storage implementations for data interfaces
* Metrics interface with a Prometheus implementation (STATSD to follow)
* Redis-backed HLL metrics are optional
Feel free to open issues in the tracker if you reach that point!
I have one other bit of feedback on the architecture, and I realise that this one might be too different from where AuthN is currently. I see that there are a small number of API endpoints that you've marked "public" that users are meant to be able to reach. In practice, the user's POSTs would always be terminated upstream from the AuthN server itself, either at a gateway or by just relaying the requests through from the front-end servers themselves. A service that's part public/part private is much more perilous than a service that is one or the other. All things considered, these public endpoints seem like a very minor convenience to the integrator. I would just make the AuthN API private, and ask users to implement the public endpoints themselves by making requests to the AuthN API.
If you DID make all endpoints private, you're no longer tied to HTTP, and you have a nice opportunity to use GRPC instead. I've recently started rolling it out in my own services, and it's unexpectedly great considering the track-record of similar ideas. It's very performant, pleasant to work with, has a mature ecosystem of surrounding tools, and you get client API implementations in a wide range of languages for "free" (or at least at a cut price).
Could you still achieve your deployment goals with a Gateway/WAF setup that proxies the user-facing endpoints? The only issue I'm presently aware of with this setup is https://github.com/keratin/authn-server/issues/8.
I see the motivation for sending user passwords straight to AuthN, but I do wonder if on balance the dangers of correctly configuring a shared public/private service don't outweigh the dangers of relaying the password through your front-end (which has to be able to access private endpoints on the AuthN service anyway). If you really didn't want your front-end to touch passwords, you could have a tiny sidecar service that only exposes the public endpoints and speaks GRPC to AuthN.
Anyway, this is well into debatable "matter of opinion" territory, and I don't want to waste your time. Thanks for publishing AuthN - I'll keep a close watch as you progress.
Keycloak (and similar) hosts and renders your login page. You customize through theming. You're expected to redirect users through a standard OAuth2/OIDC flow on a different domain.
AuthN doesn't render any HTML. That's all you, from start to finish. This means you have control over the UX and can build the login page directly into your own app, just like you would when using an auth library in a typical monolith.
EDIT: I've been trying out keycloak and it looks great but I've always assumed I just had not figured out how to make that happen. As the documentation is quite large and of the harder kind.
/keratin/authn-server/docs/config.md
which is a 404 presumably instead of
/keratin/authn-server/blob/master/docs/config.md
Creating authnserver_server_1 ... done
TEST_REDIS_URL=redis://127.0.0.1:8701/12 \
TEST_MYSQL_URL=mysql://root@127.0.0.1:8702/authnservertest \
go test ./data/... ./models/... ./tokens/... ./ops/... ./config/... ./lib/... ./api/... ./services/... .
[mysql] 2017/11/08 13:27:23 packets.go:33: unexpected EOF
[mysql] 2017/11/08 13:27:23 packets.go:33: unexpected EOF
[mysql] 2017/11/08 13:27:23 packets.go:33: unexpected EOF
--- FAIL: TestAccountStore (0.01s)
--- FAIL: TestAccountStore/MySQL (0.00s)
Error Trace: account_store_test.go:43
Error: Received unexpected error driver: bad connection
Connect
github.com/keratin/authn-server/data/mysql.ensureDB
/home/luser/go/external/src/github.com/keratin/authn-server/data/mysql/db.go:71
github.com/keratin/authn-server/data/mysql.TestDB
/home/luser/go/external/src/github.com/keratin/authn-server/data/mysql/db.go:27
github.com/keratin/authn-server/data_test.TestAccountStore.func3
/home/luser/go/external/src/github.com/keratin/authn-server/data/account_store_test.go:42
testing.tRunner
/usr/local/stow/go-1.9.0/go/src/testing/testing.go:746
runtime.goexit
/usr/local/stow/go-1.9.0/go/src/runtime/asm_amd64.s:2337
ensureDB
github.com/keratin/authn-server/data/mysql.TestDB
/home/luser/go/external/src/github.com/keratin/authn-server/data/mysql/db.go:29
github.com/keratin/authn-server/data_test.TestAccountStore.func3
/home/luser/go/external/src/github.com/keratin/authn-server/data/account_store_test.go:42
testing.tRunner
/usr/local/stow/go-1.9.0/go/src/testing/testing.go:746
runtime.goexit
/usr/local/stow/go-1.9.0/go/src/runtime/asm_amd64.s:2337
FAIL
FAIL github.com/keratin/authn-server/data 0.040s
? github.com/keratin/authn-server/data/mock [no test files]
? github.com/keratin/authn-server/data/mysql [no test files]
ok github.com/keratin/authn-server/data/redis 0.771s
? github.com/keratin/authn-server/data/sqlite3 [no test files]My current plan is to set up a HackerOne page. I know that bug bounties don't replace good penetration testing, but it's a start.
It would be much more interesting to me if it also did Oauth2 login with Google/Facebook/Twitter/etc.
> It would be much more interesting to me if it also did Oauth2 login with Google/Facebook/Twitter/etc.
Totally agreed. The reason I designed AuthN around accounts first is because I believe that's the best way to launch an app. OAuth2 and OIC logins are powerful, but they're secondary to the classic login.
Service tests[1] are the main unit tests, and use mock implementations of the data store interfaces.
Data (DAO) tests[2] are generally run across every implementation using only the public interface. This helps me stay sane with the mock implementations.
The API tests[3] are integration tests, and use Go's excellent httptest package to boot a real server and execute real HTTP commands.
[1] https://github.com/keratin/authn-server/tree/master/services
[2] https://github.com/keratin/authn-server/tree/master/data
[3] https://github.com/keratin/authn-server/tree/master/api/acco...
https://github.com/keratin/authn-server/blob/master/Makefile...
Notice it runs "go test $(shell glide nv)"
`glide nv` is a command that gets you all packages except the vendor directory https://github.com/Masterminds/glide#glide-novendor-aliased-...
then read the go doc for test https://golang.org/cmd/go/#hdr-Test_packages:
> 'Go test' recompiles each package along with any files with names matching the file pattern "*_test.go".
Nonsense.
Here are a few references that leap to mind (I keep the first printed out and pinned right next to my monitor).
https://gist.github.com/jboner/2841832
https://www.quora.com/What-are-some-mind-blowing-facts-about...
This is a ridiculous comparison though. When have you ever seen a situation where a micro service was accessed over the network to fetch something that would have been in L1 cache? And yes, having a value "in memory" is faster, but in most cases these days things aren't "in memory", they're "in memory in memcached, somewhere in the cluster, probably not on the local server so you have to go over the network anyway".
Just out of curiosity though, I tested on our network. Our RTT between servers is less than half a millisecond. Our fastest service can get a cached reply back to the calling service in 35ms, whereas many calls are >100ms and some are much longer (e.g. initial login calls). It would take a lot of external login calls to noticeably (to the user) increase even the fastest service.
We also use Redis and memcached, and spread those across other services. Excluding specifically HTTP overhead and just looking at network latency, a few memcached calls which hit non-local servers in the cluster cost us as much in latency as one HTTP call would.
One point of context I'd like to inject here is that chatter between AuthN and a host app is pretty minimal. Aside from executing admin actions like locking an account, the main dependency is fetching a public key to verify JWTs. This public key can be cached using a standard key fingerprint, which means it only needs to happen once per process.
Architecture does matter, and I've been pretty happy with how AuthN's boundary has played out.
Microservices can lead to better performance by making for smaller, more clearly defined codebases, fewer unnecessary imports, and so on. They can also be easy to scale, because you can scale specifically that one component (e.g. identity management) by moving it to a separate database server.
We use microservices, and we have probably close to 2TB of MySQL database, but because we have our services and their database schemas cleanly separated, we can break that up into a set of databases which all fit into memory and one database which, being basically append-only, doesn't need to access historical data and so doesn't need to fit entirely in RAM.
It also lets us easily look at our cluster stats and see where bottlenecks are, by easily seeing which servers or services are under load.
We do pay a penalty for this; layers of indirection, network latency, protocol overhead, serialization/deserialization, and so on, but designing our systems like that from the start lets us tackle those problems at the start and account for them in our design.
If you have a substantive point to make, make it thoughtfully; if you don't, please don't comment until you do.