2. Bundle config

Last modified by Ashterix on 2024/05/16 11:31

All bundle settings are located in the file config/packages/ufo_json_rpc.yaml.

It is possible to configure parameters for API security and some data format parameters that are returned when requesting documentation.

The security block

Currently, the only mechanism for securing access to your API is setting up an access key check (api_token).

Parameter protected_methods

This parameter takes an array of HTTP method names that should be protected.

By default, protection is enabled only for the POST method. You can:

  • specify an empty array [] to make the API completely open
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   security:
       protected_methods: []
  • additionally protect the GET method, making the documentation request inaccessible without a token in the request headers
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']

If you are protecting your API through protected_methods, you need to set up the tokens that will grant access.

First of all, you need to decide on the name of the token.

Parameter token_key_in_header

The RpcSecurity component will look for a specific key in the request headers, which you can set in the package settings, the default value is token_key_in_header: 'Ufo-RPC-Token', you can set any other value that meets the following requirements.


Requirements for forming HTTP protocol headers

Parameter clients_tokens

Now you need to specify an array of client tokens that will have access to the API.

There is some variability here.

Tokens in parameters

NOT RECOMMENDED!!!

This approach is only permissible for local API testing

It is possible to hardcode tokens directly in the settings file.

This is bad from a security standpoint, as if the code is stored in a public repository, everyone will have access to this token.

config/packages/ufo_json_rpc.yaml
1
2
3
4
5
6
7
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']
       token_key_in_header: 'Ufo-RPC-Token'
       clients_tokens:
            - 'ClientTokenExample'  # hardcoded token example. Importantly!!! Replace or delete it!
            

Tokens in environment variables

This is the most appropriate mechanism if you are developing a service for a distributed backend written in SOA (Service-Oriented Architecture). Typically, in such a case, you need to open access to the API to one or a limited number of client applications, and key updates will not occur too frequently.

In this case, you can write tokens in environment variables (file .env.local during local development). This mechanism is secure enough in terms of access storage.

.env.local
1
2
TOKEN_FOR_APP_1=9363074966579432364f8b73b3318f71
TOKEN_FOR_APP_2=456fg87g8h98jmnb8675r4445n8up365
config/packages/ufo_json_rpc.yaml
1
2
3
4
5
6
7
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']
       token_key_in_header: 'Ufo-RPC-Token'
       clients_tokens:
            - '%env(resolve:TOKEN_FOR_APP_1)%'   # token example from .env.local
            - '%env(resolve:TOKEN_FOR_APP_2)%'   # token example from .env.local

User Tokens

Assuming that you might need to make personal keys for the users of your app, possibly you would want to implement limits or other restrictions.
In such a case, you do not need to list the tokens in the configs, you can store them in a database or another place according to your business logic. The only requirement is that you have a service that can verify whether a given token exists.

In order for the JsonRpcServer to use your logic, you will have to implement your own class that implements the interface Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator

Example of a custom token validator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Services\RpcSecurity;

use App\Services\UserService;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator;
use Ufo\RpcError\RpcInvalidTokenException;

class UserTokenValidator implements ITokenValidator
{

   public function __construct(protected UserService $userService) {}

   public function isValid(string $token): true
   {
       try {
           this.userService.getUserByToken(token);
           return true;
        } catch (UserNotFoundException e) {
           throw new RpcInvalidTokenException(previous: e);
        }
    }
}

IMPORTANT!!!
The isValid method must return true if the token exists and is valid, or throw Ufo\RpcError\RpcInvalidTokenException otherwise.

After that, you need to specify in the file config/services.yaml that classes dependent on the interface ITokenValidator should accept your new class.

config/services.yaml
1
2
3
4
5
6
7
8
9
10
parameters:
  # some parameters list
  # ...

services:
  # some services list
  # ...

   Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator:
       class: App\Services\RpcSecurity\UserTokenValidator

Block async

This block is for configuring asynchronous transport.

Add the parameter rpc_async which contains a string in DSN format. This string is the configuration for Symfony Messenger, and it points to the asynchronous transport where the RPC Server will be waiting for incoming requests if you have a consumer running (php bin/console messenger:consume rpc_async). For a more detailed understanding of this process, read the Symfony Messenger documentation.

config/packages/ufo_json_rpc.yaml
1
2
3
4
ufo_json_rpc:
   async:
       rpc_async: '%env(resolve:RPC_TRANSPORT_DSN)%'

This configuration implies that you have an environment variable RPC_TRANSPORT_DSN set, which contains the DSN string.

The docs block

This block configures the generation of documentation when you make a GET request on the RPC Server

Section response

Contains settings that correspond to the content of the response.

Parameter key_for_methods

This parameter allows you to specify the name of the key in the response that will contain an array of available services.

The default value is methods. It can have any string value.

config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: methods
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: services
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: some_custom_key

Parameter async_dsn_info

Responsible for displaying information about asynchronous transport in the documentation.

config/packages/ufo_json_rpc.yaml
1
2
ufo_json_rpc:
   async_dsn_info: true # or false

Documentation Example

GET: /api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
   "envelope": "JSON-RPC-2.0/UFO-RPC-6",
   "contentType": "application/json",
   "description": "",
   "transport": {
       "sync": {
           "scheme": "http",
           "host": "example.com",
           "path": "/api",
           "method": "POST"
        },
       "async": {
           "scheme": "amqp",
           "user": "{user}",
           "pass": "{pass}",
           "host": "async_rabbit",
           "port": 5672,
           "path": "/%2f/json-rpc"
        }
    },
   "methods": {
       ...
    }
}

Do not worry about the security of your authorization data contained in the DSN.

The documenter is designed in such a way that before displaying DSN information, it removes user data and passwords, as well as other sensitive information, such as tokens, secret keys, etc.

The template for protecting data is /([\w\d_]*(?:secret|access|token|key)[_\w]*)=((?:\w|\d)+(?=&?))/.

Example: 

RPC_TRANSPORT_DSN=https://sqs.eu-west-3.amazonaws.com/123456789012/messages?access_key=AKIAIOSFODNN7EXAMPLE&secret_key=j17M97ffSVoKI0briFoo9a
1
2
3
4
5
6
7
8
{
   "async": {
       "scheme": "https",
       "host": "sqs.eu-west-3.amazonaws.com",
       "path": "/123456789012/messages",
       "query": "access_key={access_key}&secret_key={secret_key}"
    }
}

Parameter validations

Responsible for displaying in the documentation methods additional blocks that indicate data validation requirements.

Currently, this block has two possible settings:

  • json_schema: <bool>
  • symfony_asserts: <bool>

In all options in this parameter, the default value is false, meaning these blocks will not appear in the documentation.
If you need any of these information blocks when requesting documentation, set the value to true.

config/packages/ufo_json_rpc.yaml
1
2
3
4
ufo_json_rpc:
   docs:
       json_schema: true
       symfony_asserts: true 

Example of documentation 

In this example, I removed the content of the objects symfony_assertions to simplify the example.
For more information on method validation, see the page Validate procedures

GET: /api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
{
   "envelope": "JSON-RPC-2.0/UFO-RPC-6",
   "transport": "POST",
   "target": "/api",
   "contentType": "application/json",
   "description": "",
   "methods": {
       "getUserNameByUuid": {
           "name": "getUserNameByUuid",
           "description": "Get username by id",
           "parameters": {
               "userId": {
                   "type": "string",
                   "name": "userId",
                   "description": "User id in uuid format",
                   "optional": false
                }
            },
           "returns": "string",
           "responseFormat": "string",
           "json_schema": {
               "$schema": "http://json-schema.org/draft-07/schema#",
               "type": "object",
               "properties": {
                   "userId": {
                       "type": "string"
                    }
                },
               "required": [
                   "userId"
                ]
            },
           "symfony_assertions": {
               "userId": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Uuid",
                       "context": {}
                    }
                ]
            }
        },
       "sendEmail": {
           "name": "sendEmail",
           "description": "Send mail",
           "parameters": {
               "email": {
                   "type": "string",
                   "name": "email",
                   "description": "",
                   "optional": false
                },
               "subject": {
                   "type": "string",
                   "name": "subject",
                   "description": "",
                   "optional": false
                },
               "text": {
                   "type": "string",
                   "name": "text",
                   "description": "",
                   "optional": false
                }
            },
           "returns": "boolean",
           "responseFormat": "boolean",
           "json_schema": {
               "$schema": "http://json-schema.org/draft-07/schema#",
               "type": "object",
               "properties": {
                   "email": {
                       "type": "string",
                       "format": "email"
                    },
                   "subject": {
                       "type": "string",
                       "minLength": 1,
                       "maxLength": 100
                    },
                   "text": {
                       "type": "string",
                       "minLength": 10
                    }
                },
               "required": [
                   "email",
                   "subject",
                   "text"
                ]
            },
           "symfony_assertions": {
               "email": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Email",
                       "context": {}
                    }
                ],
               "subject": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
                       "context": {}
                    },
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Length",
                       "context": {}
                    }
                ],
               "text": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
                       "context": {}
                    },
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Length",
                       "context": {}
                    }
                ]
            }
        }
    }
}