Version 6. Bundle config
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
2
3
security:
protected_methods: []
- additionally protect the GET method, making the documentation request inaccessible without a token in the request headers
2
3
security:
protected_methods: ['GET', 'POST']
protected_methods, you need to set up the tokens that will grant access.
If you are protecting your API throughFirst 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
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.
2
3
4
5
6
7
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.
2
TOKEN_FOR_APP_2=456fg87g8h98jmnb8675r4445n8up365
2
3
4
5
6
7
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}
}
After that, you need to specify in the file config/services.yaml that classes dependent on the interface ITokenValidator should accept your new class.
2
3
4
5
6
7
8
9
10
# 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.
2
3
4
async:
rpc_async: '%env(resolve:RPC_TRANSPORT_DSN)%'
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.
2
3
docs:
key_for_methods: methods
2
3
docs:
key_for_methods: services
2
3
docs:
key_for_methods: some_custom_key
Parameter async_dsn_info
Responsible for displaying information about asynchronous transport in the documentation.
2
async_dsn_info: true # or false
Documentation Example
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": {
...
}
}
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.
2
3
4
docs:
json_schema: true
symfony_asserts: true
Example of documentation
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": {}
}
]
}
}
}
}