A simple authentication service.
-
Dependency management using poetry
- If you'd rather not install poetry on your system, you can download the wheel from the Github Releases page and install the
authhero
package in a local virtual environment.
- If you'd rather not install poetry on your system, you can download the wheel from the Github Releases page and install the
-
PostgreSQL
- The app will connect to a Postgres database running on localhost
- If you'd rather not install Postgres, but still want to run the tests, you can skip the tests that need a database with:
pytest -m "not functional"
- Set up the local development and test databases with:
# Assumes username: postgres, without password (configurable)
./scripts/init_local_db.py
You can either setup a local virtual environment with poetry or install the authhero
package with a wheel from the project's Releases page on Github.
Run a local Flask dev server from the src
directory with:
flask run
You should be able to see the Swagger API documentation with fully functioning UI to call the API on http://127.0.0.1:5000/.
- Register a new user with
/auth/register
- Login with entered credentials at
/auth/login
- Save the
api_key
token provided in the response - In the Swagger Docs, enter the token in the
Authorize
menu on the top-right
- Save the
- Interact with the User CRUD API at
/users/*
- Can view all active users
- Logging out will invalidate all of users active tokens, and the user will need to login again, and request a new token
- Deleting a user can only be done on the logged in user, and it is a soft-delete, where the user's API keys will no longer be able to be used for authentication.
There are two kinds of tests: "simple" unit tests and functional tests that, while not being true end-to-end tests, they test the full functionality, including the database.
- Inside a virtual env, run
pytest
from the repo root. - Run only the unit tests with `pytest -m "not functional"
Due to time, only partial code coverage.
- Picked MVC to structure the code, since it should be a simple project
- Would try the Repository pattern for more complex use cases
- Added a layer for business logic, so as to not pollute models or views, in
services.py
files
- Flask as a simple web server, with the following libraries:
- flask-restx - a handy library for building REST APIs in Flask, comes with integrated Swagger Docs.
- SQLAlchemy - a database toolkit
- bcrypt - for secure password hashing
- Configuration through
Config
objects - Storing of hashed passwords using
bcrypt
(hashing and salt using default configuration) - Minimal validation in User CRUD, done using
flask_restx.reqparse
- No AUTOCOMMIT, prefer "Unit of Work" pattern
- PostgreSQL instead of SQLite
- Slightly more complicated to set up, but since I had enough time to do so, I did :)
- Much more likely to be used in a real product
- Two entities:
User
andApiKey
(one-to-many relationship) - All models have the
created_on
andupdated_on
attributes for easy auditing and data analysis - Implemented "soft" deletion of Users, with the
is_active
flag
- Decided to implement only Token-authentication, due to time restrictions
- Currently can only authenticate from custom header: "X-API-KEY"
- Authentication via querystring was not implemented due to time
- Use pydantic for configuration and secrets handling
- Also add a
ProductionConfig
using this
- Also add a
- Document all possible responses and status codes
- Did not cover all in docs due to time
- Handle all database errors since it's quite an important dependency
- Better test coverage - would've done more with more time
- Also different tests:
- end-to-end
- smoke tests
- load testing, for example with locust
- Also different tests:
- CI/CD system
- Docker container
- Kubernetes configuration
User.id
currently is currently anint
that autoincrements from1
- In a distributed authentication system, would prefer
uuid
overint
to avoid collision - If still
int
, would configure primary key generation differently
- In a distributed authentication system, would prefer
- Introduce role-based access and have a more sophisticated authorization system, with at least an "admin" role
- Have the authentication system be more abstract so that it's extendable to more ways of authenticating and/or providers, for example to be able to add JWT and OAuth
Would also do the following improvements:
- Split off
User
attributes likefirst_name
andlast_name
into a new table calledProfile
, so as to not affect theUser
model that is used for authentication. - Do not return
int
s as status codes in views, but use classes provided by Flask - As it is, I did not include any logging in the solution and instead relied on built in error-handling of the libraries used. For an actual solution, I would add way custom exceptions per level of abstraction, and then error-handling and logging.
- In an actual production-ready solution, I would also add integrations with a monitoring platform like Prometheus/Grafana, as well as Infrastructure-as-Code, like Terraform or Pulumi.