
Show HN: Keratin AuthN – Accounts and Auth Microservice in Go - cainlevy
https://keratin.tech
======
praxxis
We're currently using this as our auth layer for
[https://empatico.org](https://empatico.org), so it's production ready!

------
krullie
How does it compare to Hydra/Dex? What I'm missing is a page that tells me
where it sits in the ecosystem. A versus page if you will.

~~~
cainlevy
I'd love to fill that in! If anyone would like a comparison, please add links
in this thread and I'll reply. Later, I can collect it into a published page.

~~~
krullie
That would be great, thanks! Here are the links to the projects I mentioned.

[https://github.com/coreos/dex](https://github.com/coreos/dex)
[https://github.com/ory/hydra](https://github.com/ory/hydra)

~~~
cainlevy
> Dex is NOT a user-management system, but acts as a portal to other identity
> providers through "connectors."

> 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.

------
rmetzler
Today I was evaluating what I we should use for something like this, a unified
Facade with an API.

We evaluated Traefik and Kong. Decision was for Kong, since we need more
features like auth, logging, rate limit.

~~~
cainlevy
Strong choice! My dream is for AuthN to provide authentication and account
functionality for folks who have not yet invested in an API gateway, and then
seamlessly plug in when their architecture matures later. Extracting user
accounts can be a difficult barrier for transitions like that.

------
cortesi
This looks pretty reasonable! I would love to see a Cloud Storage backend. A
minor quibble is that I think that managing your own metrics in Redis is
probably not the simplest or most flexible approach - instead, you should
consider exposing a /metrics endpoint that can be ingested by the user's
monitoring tool of choice (Prometheus/InfluxDB/etc).

~~~
cainlevy
Thanks!

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.

~~~
cortesi
It looks like the /stats endpoint consults the Redis database and returns
processed data. If we're aggregating metrics externally, it would be more
idiomatic for this to be totally stateless and just expose hot metrics - it's
the aggregator's job to store and process the data. This can be quite painless
using something like the Go Prometheus library.

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. :)

~~~
cainlevy
That all sounds like a direction I'd happily consider:

* 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!

~~~
cortesi
Good stuff.

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).

~~~
cainlevy
My intention for the user-facing endpoints is that the host app will never
need to see or accidentally log a user's password. It's a pattern inspired by
credit card vaults.

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](https://github.com/keratin/authn-server/issues/8).

~~~
cortesi
Sure - in my case this would be a load balancer on Google Cloud configured
through a Kubernetes ingress. All the public paths available on the Auth
microservice would have to be listed explicitly. You have to be very sure in
this scenario that only the public URLs are reachable.

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.

------
kuschku
Am I missing something, or does this really have no support for TOTP/HOTP? An
authentication system without 2FA or U2F support in 2017 seems... lacking (or
unfinished).

~~~
cainlevy
It's on my roadmap. Prioritizing is hard. :/

------
nmenglund
How does AuthN compare to Keycloak?

[http://www.keycloak.org](http://www.keycloak.org)

~~~
cainlevy
Keycloak does some really great things. It does require managing a Java
runtime though, and is missing the streamlining that allows AuthN to run as an
invisible API.

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.

~~~
guywhocodes
What, I can't simply post to any login/register/logout uri's and expect either
a redirect with configuring headers back or an object that let's me manage the
token manually?

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.

------
tetraodonpuffer
the toplevel links (implementation / deployment / configuration) don't work
for me, they go to say

/keratin/authn-server/docs/config.md

which is a 404 presumably instead of

/keratin/authn-server/blob/master/docs/config.md

~~~
cainlevy
Fixed, thanks!

~~~
tetraodonpuffer
no problem, btw, the first time I did make test I got this, but after that I
was not able to repro it again and make test always worked, pasting here in
case you are interested

    
    
        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]

~~~
cainlevy
Thanks for the report! I believe I've tracked this down to an initialization
routine that MySQL goes through on the first boot. It happens after docker-
compose unblocks. Likely a wontfix. :/

~~~
tetraodonpuffer
cool you found the root cause, I take this is not the code path that runs in
CI? otherwise I'd think you'd hit it all the time unless somehow the MySQL
container sticks around...

~~~
cainlevy
Yeah. I think Travis provides a warmed server.

------
ehc
Has Keratin gone through any security research / penetration testing?

~~~
cainlevy
I would dearly love that! The answer is not yet. Can you recommend any testers
that are OSS-friendly?

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.

------
galvanium
Is SAML supported and if not is it planned in the near future ?

~~~
cainlevy
I'm currently investing in JWT and have not done enough research on SAML to
make it part of my plans. Happy to learn more.

~~~
galvanium
You might want to take a look at AWS-Cognito and their SAML and OAuth2.0
integration. For our enterprise startup, we are leaning towards Cognito to
provide multi-tenant UserPools with customer specified authentication
providers.

------
xena
Is there an API client for this in Go?

~~~
cainlevy
Seems like an oversight, doesn't it? I've created an issue to track here:
[https://github.com/keratin/authn-
server/issues/15](https://github.com/keratin/authn-server/issues/15)

------
brianolson
It's just name/pass

It would be much more interesting to me if it also did Oauth2 login with
Google/Facebook/Twitter/etc.

~~~
cainlevy
Yeah, name/pass sounds pretty simple, doesn't it? But doing it correctly,
securely, with a service architecture? That gets interesting.

> 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.

------
je42
almost no test coverage. did i miss them ? for proper use in production you
would need to have hundreds of unittests and a whole bunch of component +
integration and e2e tests.

~~~
cainlevy
Tests are colocated inside packages (folders) using a `_test.go` convention.

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](https://github.com/keratin/authn-
server/tree/master/services)

[2] [https://github.com/keratin/authn-
server/tree/master/data](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/tree/master/api/accounts)

------
ahoka
"Microservices perform better, especially when written in Go."

Nonsense.

~~~
ShabbosGoy
Sort of agree, that's a very noisy and broad statement. I'd argue that the
underlying I/O and event loop implementation matters more. At the end of the
day it's all about highly available systems. Am I wrong?

~~~
andrewstuart2
Microservices need to be divided up _very_ carefully, with full knowledge of
the latency trade-off of moving functionality across a network gap. A same-
datacenter round-trip takes 5,000 times longer than a main memory access, and
1,000,000 times longer than an L1 cache access. There's no silver bullet in
performance or scalability. Each solution will require trading off advantages
that matter less to achieve business goals.

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://gist.github.com/jboner/2841832)

[https://www.quora.com/What-are-some-mind-blowing-facts-
about...](https://www.quora.com/What-are-some-mind-blowing-facts-about-
computers/answer/Thomas-Cormen-1)

~~~
danudey
> A same-datacenter round-trip takes 5,000 times longer than a main memory
> access, and 1,000,000 times longer than an L1 cache access.

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.

