access-control
A REST API that authorizes access to your services via JWT, with a simple set up process without roles for now, just users.
Installation
git clone https://github.com/maxiancillotti/access-control
Run
Containerized API & Database
Use Docker Compose to build and run the API with its own database in dedicated containers for each of them.
docker compose -f "docker-compose.yaml" up -d --build
Only by executing this command on the CLI with the Compose file just as it was provided you can take a look at how the API works.
Voilà, now access-control is up and running.
Consider:
-
Config: Aside from testing you will have to set up some parameters in the Compose file. More on this below.
-
Storage: The SQL Server image is not so lightweight. You will need 1.65GB of free space. But don't fret, you can connect an already existent instance so you can save up your drive space.
Containerized API linked to an existent SQL Server instance
Use Docker Compose to build and run the API.
docker compose -f "docker-compose_API_ONLY.yaml" up -d --build
But before attempting this, you need to set up some parameters in the Compose file.
To create the database, scripts are provided in ./db/sqlserver/db_create_scripts/ to create schema and insert basic data.
Config
Find all the config variables in the Compose file of your preference.
API Container
-
Ports: Map a port in the host to a port in the container to run the API's HTTP server and access it from the host. Change the env var HTTPSERVER_HOST_PORT accordingly.
-
JWT Secret Keys: 256 bits strings that are used to sign and encrypt the tokens. Do not use the examples on production.
-
Timeouts / Duration strings: Time expressions composed by a decimal number and a string suffix that indicates a unit, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
-
Database: connection parameters. Instance is optional. When running DB Container, only DB_PASSWORD env var needs to be updated replicating this action in said container config (see Password below).
Database Container
-
Ports: Map a port in the host to the 1433 port in the container to access the database from the host.
-
Password: set in build arg MSSQL_SA_PASSWORD and in the healthcheck test command after the -P flag between the double quotation marks.
-
SQL Server Edition: Change build arg MSSQL_SA_PASSWORD if needed. Default: Developer.
-
Await time (in seconds) until server bootup: Change build arg SECS_AWAIT_SVR_BOOTUP if needed. Default: 100. The SQL Server needs to be prepared before any connection attemp or it will fail. The database will be created at build time. Set a time that you consider enough in your environment to wait for the server to be initialized before attempting to create the database.
Usage
First steps
Set up some data in the system:
- Change the default password for your Admin.
- Create Users to which Permissions will be granted.
- Create Resources to which you want to control access.
- Create Permissions so that your Users can access those Resources.
Domain Models & Use Cases
Admins
Are the system administrator's users that will be necessary to authenticate everytime a read or write action is performed on any of the models.
A default admin is created from the outset, which username and password are, respectively: "admin" and "APIUserPassword". Change this password or create a new admin and disable the former.
X-Admin Authorization
Authenticates an admin vía http header "Authorization". Basically equal to Basic Authorization but with a custom "X-Admin" type instead of "Basic" as a prefix in the value of the header. Credentials formating are the same.
CREATE
Request
POST /api/admins HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"username": "admindevops"
}
Body fields:
- username (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":4,
"username":"admindevops",
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- id (int)
- password (string)
- username (string)
Password is randomly generated.
RETRIEVE by username
Request
GET /api/admins HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"username": "admindevops"
}
Body fields:
- username (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":4,
"username":"admindevops",
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- id (int)
- password (string)
- username (string)
UPDATE Password
Request
PATCH /api/admins/{id}/password HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- password (string)
Password is randomly generated.
UPDATE Enabled State
Request
PATCH /api/admins/{id}/enabled-state HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"enabled_state": true
}
URL parameters:
- id (int)
Body fields:
- enabled_state (bool)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "admin enabled state updated OK"
}
Body fields:
- success_message (string)
DELETE
Request
DELETE /api/admins/{id} HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "admin deleted OK"
}
Body fields:
- success_message (string)
Users
Are the clients of the APIs you want to control access to.
CREATE
Request
POST /api/users HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"username": "apicustomers"
}
Body fields:
- username (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":4,
"username":"apicustomers",
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- id (int)
- password (string)
- username (string)
Password is randomly generated.
RETRIEVE by username
Request
GET /api/users HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"username": "apicustomers"
}
Body fields:
- username (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":4,
"username":"apicustomers",
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- id (int)
- password (string)
- username (string)
UPDATE Password
Request
PATCH /api/users/{id}/password HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"password":"_eI4PH0 ^4AwZ+_U/ F*;4w\u003cysAf'~Ucs${gS{^4XguO2QE_|Q;:WI%\u003eKB(t}xWq"
}
Body fields:
- password (string)
Password is randomly generated.
UPDATE Enabled State
Request
PATCH /api/users/{id}/enabled-state HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"enabled_state": true
}
URL parameters:
- id (int)
Body fields:
- enabled_state (bool)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "user enabled state updated OK"
}
Body fields:
- success_message (string)
DELETE
Request
DELETE /api/users/{id} HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "user deleted OK"
}
Body fields:
- success_message (string)
Resources
The resources to which you need to control access.
CREATE
Request
POST /api/resources HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"path": "/customers"
}
Body fields:
- path (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":2,
"path": "/customers"
}
Body fields:
- id (int)
- path (string)
DELETE
Request
DELETE /api/resources/{id} HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "resource deleted OK"
}
Body fields:
- success_message (string)
RETRIEVE by Path
Request
GET /api/resources HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"path": "/customers"
}
Body fields:
- path (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":2,
"path": "/customers"
}
Body fields:
- id (int)
- path (string)
RETRIEVE All
Request
GET /api/resources HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id":2,
"path": "/customers"
},
{
"id":3,
"path": "/products"
}
Body fields:
- id (int)
- path (string)
HTTP Methods
Find the IDs associated to each of the HTTP Methods so you can use it to grant permissions.
RETRIEVE by Name
Request
GET /api/http-methods HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"name": "POST"
}
Body fields:
- name (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1,
"name": "POST"
}
Body fields:
- id (int)
- name (string)
RETRIEVE All
Request
GET /api/http-methods HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1,
"name": "POST"
},
{
"id": 2,
"name": "GET"
},
...
Body fields:
- id (int)
- name (string)
Users REST Permissions
Having already created Users, Resources and looking up the HTTP Methods IDs, you can use this data to grant permissions for your REST APIs.
CREATE
Request
POST /api/users-rest-permissions HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"user_id": 1,
"permission": {
"resource_id": 2,
"method_id": 3
}
}
Body fields:
- user_id (int)
- resource_id (int)
- method_id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"user_id": 1,
"permission": {
"resource_id": 2,
"method_id": 3
}
}
Body fields:
- user_id (int)
- permission (obj{resource_id, method_id})
- resource_id (int)
- method_id (int)
DELETE
Request
DELETE /api/users-rest-permissions HTTP/1.1
Content-Type: application/json
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
{
"user_id": 1,
"permission": {
"resource_id": 2,
"method_id": 3
}
}
Body fields:
- user_id (int)
- permission (obj{resource_id, method_id})
- resource_id (int)
- method_id (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "permissions deleted OK"
}
Body fields:
- success_message (string)
RETRIEVE All by UserID
Request
GET /api/users/{userID}/rest-permissions HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- userID (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"user_id": 1,
"permissions_ids": {
"resource_id": 2,
"method_ids": {
1,
2,
3,
}
}
}
Body fields:
- user_id (int)
- permissions_ids (obj{resource_id, method_ids})
- resource_id (int)
- method_ids ([]int)
RETRIEVE All by UserID with Descriptions
Request
GET /api/users/{userID}/rest-permissions-with-descriptions HTTP/1.1
Authorization: X-Admin YWRtaW46QVBJVXNlclBhc3N3b3Jk
URL parameters:
- userID (int)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"user_id": 1,
"permissions": {
{
"resource": {
"id": 1,
"path": "/customers"
},
"methods": {
{
"id": 1,
"name": "POST"
},
{
"id": 2,
"name": "GET"
}
}
},
{
"resource": {
"id": 2,
"path": "/products"
},
"methods": {
{
"id": 1,
"name": "POST"
},
{
"id": 2,
"name": "GET"
}
}
}
}
}
Body fields:
- user_id (int)
- permissions ([]obj{resource, []methods})
- resource (obj{id (int), path (string)})
- methods ([]obj{id (int), name (string)})
Error responses
For any of the former cases.
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error_message":"error creating admin: username already exists"
}
Body fields:
- error_message (string)
Auth Workflow
User Authentication
Creates a JSON Web Token (JWT) using Basic Authorization with the API User credentials.
Default duration: 30 min. Update AUTH_JWT_EXPIRATION_TIME_DURATION env var in Compose file if needed.
The User must request the token to access-control before attempting a request to a REST API Resource.
Request
POST /api/users/token HTTP/1.1
Authorization: Basic dXNlcjpwYXNzd29yZA==
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..hcXjpXviA8_u8rUG5ZQlgA.sdqsDwz_mxzP7aTYGi3k5kZqbq1eiZ1K290-zbK0lDJVrOUJNdhdixAsKFx6GntEbc8IrilnbRhzol0QuNyPsXpJX14dWdoSHlGfA6MHSxbxvv3vuReSEse7yzFV6T8euDTjqrAveb2NgplA2B_c7mu2X-LWfUrWv1UdhJc8GlHig-SXQVgXsrAoR-D593NzcxdQMNFEqlu-8y_l7R6Lq4WQ6vJVIg6vxmqgNVZejpajHB7mnbt7-h3wyE8VQrqnCOJJI2h1jylq9ilMqyTHBYIy0CQA3058-H_1GhfENDM.IpA-BB2RHIDv4toaOtFWlw"
}
Body fields:
- token (string)
User's Request to a Resource
User must make a request to a Resource using Authorization header, with the "Bearer" type prefix, so it can be authorized.
Request
GET /api/customers HTTP/1.1
Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.._uIIQ_8G5-G3XGGaGGkyWQ.zuZDQB0q7XP4I99QytMcE06Rm9Ei5jZ988nc0E9LmsQgkftwPeFl1ucRplYePOA54k7B7wJQ6sM0qjfR3PUC_DHBRUCkeeoHN9PscN8UH25_P9qBV7LnP4ZXdoXkHamy98vzzXEJcNf3DSuRfP9cvBBy_qHWpO3q6wZ0udxDr6408gw2NfnFPQck1iXnGrEP60tp66c9krMY4Ls5f0Kw304ssvvRlQtCt_RVC7FntHq6szPlBIC6qS3rHbliq_R502aXiwLWLJYjuC-weZKXyrqHfpyJA6a_4zu476JktKU.Cvm8JmcuMKEAsQvjG2omcA
Authorization
The requested API must make a request to access-control to authorize the User to access the Resource executing a given HTTP Method.
Request
POST /api/users/token/authorize HTTP/1.1
Content-Type: application/json
{
"token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.._uIIQ_8G5-G3XGGaGGkyWQ.zuZDQB0q7XP4I99QytMcE06Rm9Ei5jZ988nc0E9LmsQgkftwPeFl1ucRplYePOA54k7B7wJQ6sM0qjfR3PUC_DHBRUCkeeoHN9PscN8UH25_P9qBV7LnP4ZXdoXkHamy98vzzXEJcNf3DSuRfP9cvBBy_qHWpO3q6wZ0udxDr6408gw2NfnFPQck1iXnGrEP60tp66c9krMY4Ls5f0Kw304ssvvRlQtCt_RVC7FntHq6szPlBIC6qS3rHbliq_R502aXiwLWLJYjuC-weZKXyrqHfpyJA6a_4zu476JktKU.Cvm8JmcuMKEAsQvjG2omcA",
"resource_requested": "/customers",
"method_requested": "GET"
}
Body fields:
- token (string)
- resource_requested (string)
- method_requested (string)
Successful response
HTTP/1.1 200 OK
Content-Type: application/json
{
"success_message": "Token authorization OK"
}
Body fields:
- success_message (string)
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.