Safe Haskell | None |
---|---|
Language | Haskell2010 |
- data SqliteJwt = SqliteJwt {}
- data Options = Options {}
- data User = User {}
- data AuthFailure
- defaults :: Options
- sqliteJwtInit :: Options -> Snaplet Sqlite -> SnapletInit b SqliteJwt
- registerUser :: Handler b SqliteJwt ()
- loginUser :: Handler b SqliteJwt ()
- requireAuth :: (User -> Handler b SqliteJwt a) -> Handler b SqliteJwt a
- createUser :: Text -> Text -> Handler b SqliteJwt (Either AuthFailure User)
- login :: Text -> Text -> Handler b SqliteJwt (Either AuthFailure User)
- jsonResponse :: MonadSnap m => m ()
- writeJSON :: (MonadSnap m, ToJSON a) => a -> m ()
- reqJSON :: (MonadSnap m, FromJSON b) => m b
Introduction
A snap middleware for implementing JWT-based authentication with user accounts persisted in a SQLite3 database. It's intended use is to protect server API routes used in single-page web applications (SPA) and mobile applications.
Passwords are hashed and salted using the BCrypt library.
See the https://github.com/nurpax/snap-reactjs-todo project for a full application using this library. It implements a todo application as an SPA using React and Redux with a Haskell API server running on Snap and uses this library to implement logins and route authentication. This blog post has a walk-through of the application's source code.
Types
Site configuration options
Tokens will expire after maxTokenExpiration
seconds. The login handler
may be extended in the future to allow setting a lower expiration time. In
that case, maxTokenExpiration
will be used as the upper limit for expiry.
Otherwise someone might modify login requests to set an infinitely long
expiration time and JWTs would never expire.
You can use the defaults
value as your
basis, overriding individual fields as necessary.
Options | |
|
User ID and login name.
If you need to store additional fields for your user accounts, persist them
in your application SQL tables and key them by userId
.
data AuthFailure Source #
Types of errors that can happen on login or new user creation.
UnknownUser | The login name does not exist. |
DuplicateLogin | The login name already exists. |
WrongPassword | Failed the password check. |
Initialization
:: Options | Site policy options |
-> Snaplet Sqlite | The sqlite-simple snaplet |
-> SnapletInit b SqliteJwt |
Initializer for the sqlite-simple JwtAuth snaplet.
If the secret random key jwtSigningKeyFname
doesn't exist in the current
working directory, a new random key will be generated. Otherwise the
existing key will be loaded as the site signing key. This key is used to
sign the JWTs generated by the login procedure.
Initialization will automatically setup SQL tables used to store user accounts. It will also automatically upgrade the SQL schema if necessary.
High-level handlers
Use these handlers to implement user registration, login and protecting routes with authentication.
The registerUser
and loginUser
handlers follow a fixed convention for
request parameters and response. To sign up a user or login an existing
user, make a POST request to a route handled by registerUser
or
loginUser
. Both require input parameters to be passed in as JSON. A
successful user creation or a login will return HTTP 400 code and reply
with a JSON object containing the JWT. Failed login attempts will reply
with an HTTP 401 error and will reply with a JSON object containing the
error message.
Use the requireAuth
wrapper for implementing routes that require
authentication. The client side is responsible for passing in a valid
JWT in the Authentication header.
If you need to customize error handling or need a different JSON schema
for communicating between the server and client, you may wish the re-
implement registerUser
and loginUser
using the low-level handlers
documented later in the API ref.
registerUser :: Handler b SqliteJwt () Source #
Create a new user.
Use a POST request for this handler with password and login encoded as JSON in the body of the request:
{ "login": <login name>, "pass": <password> }
A successful login will reply with an HTTP 400 code and the following JSON:
{ "token": <JWT> }
The returned token can be used to make authenticated requests.
Login and user registration errors will be reported with an HTTP error code 401 and the error message will be sent as a JSON object:
{ "error": <message> }
loginUser :: Handler b SqliteJwt () Source #
Login an existing user.
Use a POST request for this handler with password and login encoded as JSON in the body of the request:
{ "login": <login name>, "pass": <password> }
A successful login will reply with an HTTP 400 code and the following JSON:
{ "token": <JWT> }
The returned token can be used to make authenticated requests.
Login and user registration errors will be reported with an HTTP error code 401 and the error message will be sent as a JSON object:
{ "error": <message> }
requireAuth :: (User -> Handler b SqliteJwt a) -> Handler b SqliteJwt a Source #
Run a handler with the currently logged in user.
Verify authentication from the JWT token passed in the Authorization
header, and run the user provided action
with the logged in user.
Use the following syntax for constructing the Authorization header:
Bearer <JWT>
where <JWT> is obtained from the "token" field of a successful call to
registerUser
or loginUser
.
On errors such as missing or malformed JWT or failure to verify the JWT, error out early and issue an HTTP 401 error.
Lower-level login handlers
Use these if you need more customized login/register user functionality.
:: Text | Login name of the user to be created |
-> Text | Password of the new user |
-> Handler b SqliteJwt (Either AuthFailure User) |
Create a new user.
:: Text | Login name of the user logging in |
-> Text | Password |
-> Handler b SqliteJwt (Either AuthFailure User) |
Login a user
Utility functions
Helper functions for JSON request parameters and JSON responses.
jsonResponse :: MonadSnap m => m () Source #