# API Access Control (Proof Of Concept)

API developed in .NET 8 that creates and validates JSON Web Tokens (JWT).

__Technologies used:__
- .NET 8.0.202

The choice of having a centralized web service for JWT management allows:

- Quick deployments: Should an update / fix be needed there is only one source code to be \
modified, tested, and deployed.
- Abstraction of complexity: Any logic regarding security validation is encapsulated and centralized in one \
application making it easy for anyone to call its endpoints.

In other words: Centralizing the JWT management will take the complexity of generating and validating JWTs away from the \
APIs and/or applications themselves. Meaning that the probability of human error introduced by the developer's \
implementation is low. It allows for any fixes or updates to be quickly rolled out.

## Endpoint Definition
JWT Orchestrator exposes two different endpoints:

### 1. JWT Generation / Regeneration
__Description:__ Endpoint that generates JWTs for any caller whose credentials are valid. \
__URL:__ `/jwt/generator` \
__Method:__ `POST` \
__Content Type:__ `application/json` \
__Request CURL:__
```
curl -x POST https://ca.api.access.control:7001/jwt/generator \
--header "Content-Type: application/json" \
--data-raw '{ "callerId": "${callerId}" , "callerSecret": "${callerSecret}" }' \
--include --insecure
```
__Response example 1 (HTTP 200 OK):__ \
This is the expected reponse when the `callerId` was found in the database and the hash `callerSecret` corresponds to the one \
in the database (among other checks, read on for details).
```
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE3MTI0NDgwMDAsImNhbGxlcklkIjoiYXBwMSJ9.Ru_JR4IZYFCwpKQ6zBXRJhd2NdiAHEzf5DSQvtIp1ic",
    "token_type": "Bearer",
    "expires_in": 7776000
}
```
__Response example 2 (HTTP 401 Unauthorized):__
This is the expected response when any of the security checks are failed
```
{
    "errorCode": "CA-JWTO-0001",
    "errorMessage": "Information of which check has failed and why."
}
```

### 2. JWT Validation
__Description:__ Endpoint that verifies the validity of a given JWT. This endpoint expects a \
provided API ID and API secret to authenticate the caller. \
__URL:__ `/jwt/validator` \
__Method:__ `POST` \
__Content Type:__ `application/json` \
__Request CURL:__
```
curl -x POST https://ca.api.access.control:7001/jwt/validator \
--header "Content-Type: application/json" \
--data-raw '{ "token": "${jwt}" , "apiId": "${apiId}", "apiSecret": "${apiSecret}" }' \
--include --insecure
```
__Response example 1 (HTTP 200 OK):__
```
{ 
    "validity": true
}
```
__Response example 2 (HTTP 200 OK):__
```
{ 
    "validity": false,
    "reason": "Information of which check has failed and why."
}
```

## Use Cases & Sequence Diagrams

The following section lists and explains the use cases to be satisfied by JWT Orchestrator.

The first step before requesting any API implementing JWT security is to get a token from the \
JWT Orchestrator. Providing valid credentials is necessary for that matter. Said credentials \
will then be validated.

The endpoint `/jwt/generator` must be `POST` requested when generating a JWT for the first time or after it has \
expired. We can consider a token has expired when a **HTTP 403 Forbidden** error sent by the \
API to the caller. At which point the API would have received a `{ validity: false }` from \
JWT Orchestrator with the reason pointing at an invalid JWT.

### Generate / Regenerate JWT (__valid__ credentials)

Best case scenario where the caller provides valid credentials.

```mermaid
%%{init:{'theme':'dark'}}%%
sequenceDiagram
    autonumber
    participant app as Application
    participant jwt as JWT Orchestrator
    participant dat as Database

    app->>jwt: POST: /jwt/generator { callerId, callerSecret }
    jwt->>dat: Check callerId, callerSecret
    dat-->>jwt: OK
    jwt->>jwt: Generate signed JWT
    jwt-->>app: Generated JWT  { access_token, token_type, expires_in }
```

#### Description:
1. The application sends a `POST` request to the JWT Orchestrator containing the application's `callerId` and `callerSecret` that are to be found \
in some secret management system. The CURL request should look similar to this:
    ```
    curl -x POST https://ca.api.access.control:7001/jwt/generator \
    --header "Content-Type: application/json" \
    --data-raw '{ "callerId": "${callerId}" , "callerSecret": "${callerSecret}" }' \
    --include
    ```
2. JWT Orchestrator receives the `callerId` and `callerSecret` and checks the existance of the `callerId` in its database and verifies if the hash of the `callerSecret` corresponds to the one in the database.
3. The information exists in the database (`OK`). This check can also be done by a stored procedure.
4. The JWT is generated with the following structure: \

    HEADER:
    ```
    {
        "alg": "HS256",
        "typ": "JWT"
    }
    ```
    PAYLOAD:
    ```
    {
        "sub": "mySubject",
        "name": "John Doe",
        "iat": 1516239022,
        "exp": 1712448000,
        "callerId": "app1",
        "apiList": "api1,api2,api60"
    }
    ```
    SIGNATURE:
    ```
    = HMACSHA256(
        base64UrlEncode(HEADER) + "." + base64UrlEncode(PAYLOAD),
        "your-256-bit-secret"
    )
    ```
    The resulting byte array of the HMACSHA256 should be encoded as base64Url.
5. The generated JWT is sent back to the application. The response body may look similar to this:
    ```
    {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteVN1YmplY3QiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTcxMjQ0ODAwMCwiY2FsbGVySWQiOiJhcHAxIn0.Ru_JR4IZYFCwpKQ6zBXRJhd2NdiAHEzf5DSQvtIp1ic",
        "token_type": "Bearer",
        "expires_in": 7776000
    }
    ```

### API call (__valid__ JWT and __valid__ caller credentials)

The following use case describes the flow of a call to a JWT-enabled API using a **valid** JWT and caller credentials.

Assuming that the application already has a valid JWT provided by the JWT Orchestrator.

```mermaid
%%{init:{'theme':'dark'}}%%
sequenceDiagram
    autonumber
    actor usr as User
    participant app as Application
    participant api as API
    participant jwt as JWT Orchestrator
    participant dat as Database

    usr->>app: Click button in GUI
    app->>api: GET: /some/resource { Authorization: Bearer <jwt> }
    api->>jwt: POST: /jwt/validator { token: <jwt>, apiId: <apiId>, apiSecret: <apiSecret> }
    jwt->>dat: Check token, apiId, apiSecret
    dat-->>jwt: OK
    jwt->>jwt: Verify JWT
    jwt-->>api: HTTP 200 OK - { validity: true }
    api-->>app: Data requested (JSON formatted)
    app-->>usr: Displays data in GUI
```

#### Description:
1. The user loads the GUI (Web or Desktop application) and/or clicks on any button that loads information from an API.
2. The application requests the necessary information from the API and sends the its JWT.
    ```
    curl -x GET https://ca.api:7001/some/resource \
    --header "Content-Type: application/json" \
    --header "Authorization: Bearer ${jwt}" \
    --data-raw '{ "data": "data" }' \
    --include
    ```
3. The API receives the JWT provided by the application and requests its validation to the JWT Orchestrator. To achieve \
that, the API will send its credentials as well to be authenticated with the JWT-Orchestrator. The request is similar to this:
    ```
    curl -x POST https://ca.api.access.control:7001/jwt/validator \
    --header "Content-Type: application/json" \
    --data-raw '{ "token": "${jwt}" , "apiId": "${apiId}", "apiSecret": "${apiSecret}" }' \
    --include
    ```
4. JWT Orchestrator will run a series of checks in the following order:
    1. The API ID exists in the database.
    2. The API Secret's hash is the same in the database.
    3. The JWT exists in the database.
    4. The JWT is not revoked (database check).
5. Database check will be OK (for the moment).
6. JWT Orchestrator will continue with the following:
    1. The JWT has a valid signature.
    2. The JWT is not expired.
    3. The caller ID is authorized to access the API ID (database check).
7. JWT Orchestrator will respond with `HTTP 200 OK - { validity: true }` to the API assuming \
all of the above is true, `HTTP 403 Forbidden` otherwise.
8. The API will respond to the application with the requested data (JSON formatted).
9. The application will display the information in the GUI.

## Database Diagram (CLOUD_JWT)
```mermaid
%%{init:{'theme':'dark'}}%%

erDiagram 
    CALLER ||--o{ ACCESS_CONTROL : has
    CALLER {
        Int PK_Id PK
        Varchar Secret "Hash of the caller's secret"
    }
    API ||--o{ ACCESS_CONTROL : has
    API {
        Int PK_Id PK
        Varchar Secret "Hash of the API's secret"
    }
    ACCESS_CONTROL {
        Varchar FK_Caller_Id FK
        Varchar FK_Api_Id FK
    }
    JWT_CONTROL {
        Int PK_Id PK
        Varchar Jwt "Generated JWT in clear"
        Bit Revoked "0 = False, 1 = True"
        DateTime Issued "JWT generation timestamp"
        DateTime Date_Ajout "Database record creation timestamp"
        DateTime Date_Maj "Record modification timestamp (if any)"
    }
```

### CALLER
This table contains a simple ID for the caller (which at the same time is the primary key) and its secret (hashed value). Every possible caller has to have a registry in this table.

### API
This table contains a simple ID for the API (which at the same time is the primary key) and its secret (hashed value). Every possible API that can be accessed by a caller has to have a registry in this table.

### ACCESS_CONTROL
Table that maps which APIs can be accessed by which callers.

### JWT_CONTROL
Table that has the history of all generated JWTs. In order to revoke or tracking purposes.

---

## FINAL Database Diagram (CLOUD_JWT)
```mermaid
%%{init:{'theme':'dark'}}%%

erDiagram 
    Consommateur ||--o{ Consommateur_Api : has
    Consommateur {
        Int Consommateur_PK PK
        Varchar Clef_Secrete "Hash of the caller's secret"
    }
    Api ||--o{ Consommateur_Api : has
    Api {
        Int Api_PK PK
        Varchar Clef_Secrete "Hash of the Api's secret"
    }
    Consommateur_Api {
        Varchar CA_PK_Consommateur_FK FK
        Varchar CA_PK_Api_FK FK
    }
    Jeton {
        Int Jeton_PK PK
        Varchar Jwt "Generated JWT in clear"
        Bit Revoque "0 = False, 1 = True"
        DateTime Date_Generation_Jwt "JWT generation timestamp"
        DateTime Date_Ajout "Database record creation timestamp"
        DateTime Date_Maj "Record modification timestamp (if any)"
    }
```

### To run this project
```
dotnet run
```
Or
```
dotnet dev-certs https --trust
```

### Personal Notes:
```
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
dotnet run --urls="https://localhost:7777"
```