An HTTP identity service based on JWT auth tokens, and built on buddy. Authenticate users while managing their passwords and auth tokens independently of the apps or services they use.
zanmi serves auth tokens in response to requests with the correct user credentials. It manages a self contained password database with configurable back ends (current support for PostgreSQL and MongoDB) and hashes passwords with BCrypt + SHA512 before they're stored. The signing algorithm zanmi signs it's auth tokens with is also configurable. RSASSA-PSS is the default, but ECDSA and SHA512 HMAC are also supported.
Other back-end services can then authenticate users by verifying these auth tokens with the zanmi keypair's public key.
zanmi is still alpha software. There are probably bugs, and the api will most likely change.
zanmi is designed to be deployed with SSL/TLS in production. User passwords will be sent in the clear otherwise.
zanmi is a way to share authentication across many independent services and front ends. it depends on a database back end and a key pair/secret to sign tokens. Both the database and the algorithm used to sign tokens is configurable. The supported databases are PostgreSQL (default) and MongoDB, and the supported token signing algorithms are RSASSA-PSS (default), ECDSA, and SHA512 HMAC. Both RSA-PSS and ECDSA require paths to both a public and private key file, and SHA512 HMAC needs a secret supplied in the server config.
To try it out in development:
-
download the latest release jar
-
generate an RSA keypair with openssl by running the following in a terminal, where
<keypair path>
is some path of your choosing:mkdir -p <keypair path> openssl genrsa -out <keypair path>/priv.pem 2048 openssl rsa -pubout -in <keypair path>/priv.pem -out <keypair path>/pub.pem
-
run either a PostgreSQL or MongoDB server including a database user that can update and delete databases.
-
download and edit the example config by adding a random string as an api key and replacing the keypair paths and database credentials with your own.
-
initialize the database by running:
ZANMI_CONFIG=<path to edited config> java -jar <zanmi release jar> --init-db
-
zanmi was designed to be run with ssl, but we'll turn it off for now. Start the server by running:
ZANMI_CONFIG=<path to edited config> java -jar <zanmi release jar> --skip-ssl
The server will be listening at localhost:8686
(unless you changed the port in
the config). zanmi speaks json by default, but can also use transit/json if you
set the request's accept/content-type headers.
There is a Clojure zanmi client, and since zanmi is just a plain http server, clients for other languages should be easy to write as long as those languages have good http and jwt libraries.
We'll use cURL to make requests to the running server to be as general as possible. Enter the following commands into a new terminal window.
Send a post
request to the profiles url with your credentials to register a
new user:
curl -XPOST --data "profile[username]=gwcarver&profile[password]=pulverized peanuts" localhost:8686/profiles/
zanmi uses zxcvbn-clj to validate password strength, so simple passwords like "p4ssw0rd" will fail validations and an error response will be returned. The server will respond with an auth token if the password is strong enough and the username isn't already taken.
Clients send user credentials with http basic auth to zanmi servers to
authenticate against existing user profiles. To verify that you have the right
password, send a post
request to the user's profile auth url with the
credentials formatted "username:password"
after the -u
command switch to
cURL:
curl -XPOST -u "gwcarver:pulverized peanuts" localhost:8686/profiles/gwcarver/auth
The server will respond with an auth token if the credentials are correct.
To reset the user's password, send a put
request to the user's profile url
with the existing credentials through basic auth and the new password in the
request body:
curl -XPUT -u "gwcarver:pulverized peanuts" --data "profile[password]=succulent sweet potatos" localhost:8686/profiles/gwcarver
The server will respond with a new auth token if your credentials are correct and the new password is strong enough according to zxcvbn
zanmi also supports resetting user passwords if they've forgotten them. First,
create a JWT of the hash {"username" : <username value> }
sha512 signed with
the api-key from the zanmi config and send a post
request with that JWT as the
ZanmiAppToken
in the authorization header to get a reset token
for that user:
curl -XPOST -H "Authorization: ZanmiAppToken <jwt>" localhost:8686/profiles/gwcarver/reset
Then send the same put
request as resetting with the current password above,
but change the authorization header value to ZanmiResetToken <reset token>
,
where <reset token>
.
curl -XPUT -H "Authorization: ZanmiResetToken <reset token>" --data "profile[password]=succulent sweet potatos" localhost:8686/profiles/gwcarver
The clojure zanmi client builds the authorization JWT used to get the reset token automatically.
In production, your back-end application should request the reset token and send that to the user's email, or some other trusted channel that will verify their identity.
To remove a user's profile from the database, send a delete request to the users's profile url with the right credentials:
curl -XDELETE -u "gwcarver:succulent sweet potatos" localhost:8686/profiles/gwcarver
See the
example config
for configuration options. The easiest way to configure zanmi is to edit the
example config file linked above and set the ZANMI_CONFIG
environment variable
when running the relase jar. You can also set the configuration from the
environment. See environ
in src/zanmi/config.clj
for environment variable
overrides.
PostgreSQL and MongoDB are the only databases supported currently, but pull requests are welcome.
There are no migrations. Use the --init-db
command line switch to set up the
database and database tables.
-
What about OAuth?
- While I do have plans to implement an OAuth2 provider based on zanmi eventually, full OAuth2 support was a little overkill for my immediate use case. I wrote zanmi because I primarily wanted to (1) share user authentication among decoupled services and (2) isolate user password storage from all the other application data.
-
How do users log out?
- zanmi only supports password database management and stateless
authentication, so there is no session management. Client applications are
free to manage their own separate sessions and use that in combination with
the
:iat
and:updated
fields of the auth tokens to support logout.
- zanmi only supports password database management and stateless
authentication, so there is no session management. Client applications are
free to manage their own separate sessions and use that in combination with
the
-
What's with the name?
- Full OAuth2 implementation
- Configurable password hashing schemes (support for pbkdf2, scrypt, etc)
- Password database back ends for MySQL, Cassandra, etc.
- More configurable password strength validations
- Shared sessions (possibly with Redis)
- Validate zanmi configuration map with clojure.spec
- More tests!
Pull requests welcome!
First install leiningen and clone the repository.
When you first clone this repository, run:
lein setup
This will create files for local configuration, and prep your system for the project.
To begin developing, start with a REPL.
lein repl
Then load the development environment.
user=> (dev)
:loaded
Run go
to initiate and start the system.
dev=> (go)
:started
By default this creates a web server at http://localhost:8686.
When you make changes to your source files, use reset
to reload any
modified files and reset the server.
dev=> (reset)
:reloading (...)
:resumed
Testing is fastest through the REPL, as you avoid environment startup time.
dev=> (test)
...
But you can also run tests through Leiningen.
lein test
Copyright © 2016 ben lamothe.
Distributed under the MIT License