Last modified by Ashterix on 2024/07/11 10:08

Hide last authors
Ashterix 25.1 1 {{box cssClass="floatinginfobox" width="400px" title="**Contents**"}}
Ashterix 23.1 2 {{toc/}}
3 {{/box}}
Ashterix 2.1 4
Ashterix 25.1 5 {{error}}
6 **WARNING!!! **This version of the documentation is out of date.
7 In JsonRpcBundle version 7, the configuration has undergone significant changes and is not backwards compatible with version 6.
8 {{/error}}
9
Ashterix 2.1 10 (% class="wikigeneratedid" %)
Ashterix 25.1 11 All bundle settings are located in the file {{code language="none"}}config/packages/ufo_json_rpc.yaml{{/code}}.
Ashterix 2.1 12
13 (% class="wikigeneratedid" %)
Ashterix 25.1 14 It is possible to configure parameters for API security and some data format parameters that are returned when requesting documentation.
Ashterix 2.1 15
Ashterix 25.1 16 = The {{code language="none"}}security{{/code}} block =
Ashterix 2.1 17
Ashterix 25.1 18 Currently, the only mechanism for securing access to your API is setting up an access key check (api_token).
Ashterix 2.1 19
Ashterix 25.1 20 == Parameter {{code language="none"}}protected_methods{{/code}} ==
Ashterix 2.1 21
22 (% class="wikigeneratedid" %)
Ashterix 25.1 23 This parameter takes an array of HTTP method names that should be protected.
Ashterix 2.1 24
25 (% class="wikigeneratedid" %)
Ashterix 25.1 26 By default, protection is enabled only for the POST method. You can:
Ashterix 2.1 27
Ashterix 25.1 28 * specify an empty array {{code language="none"}}[]{{/code}} to make the API completely open
Ashterix 2.1 29
Ashterix 23.1 30 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
31 ufo_json_rpc:
32 security:
33 protected_methods: []
34 {{/code}}
Ashterix 2.1 35
Ashterix 25.1 36 * additionally protect the GET method, making the documentation request inaccessible without a token in the request headers
Ashterix 2.1 37
Ashterix 23.1 38 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
39 ufo_json_rpc:
40 security:
41 protected_methods: ['GET', 'POST']
42 {{/code}}
Ashterix 2.1 43
Ashterix 25.1 44 (% id="cke_bm_164641S" style="display:none" %) (%%)If you are protecting your API through {{code language="none"}}protected_methods{{/code}}, you need to set up the tokens that will grant access.
Ashterix 2.1 45
Ashterix 25.1 46 First of all, you need to decide on the name of the token.
Ashterix 2.1 47
Ashterix 25.1 48 == Parameter {{code language="none"}}token_key_in_header{{/code}} ==
Ashterix 2.1 49
Ashterix 25.1 50 The {{code language="none"}}RpcSecurity{{/code}} component will look for a specific key in the request headers, which you can set in the package settings, the default value is {{code language="none"}}token_key_in_header: 'Ufo-RPC-Token'{{/code}}, you can set any other value that meets the following requirements.
Ashterix 2.1 51
Ashterix 25.1 52 {{spoiler title=" Requirements for forming HTTP protocol headers"}}
53 Requirements for HTTP header names are not strictly regulated regarding capitalization since HTTP headers are case-insensitive. However, there are some common practices and standards that are usually followed for better readability and consistency:
Ashterix 2.1 54
Ashterix 25.1 55 - Capitalization: Typically, HTTP header names are written using CamelCase, where each word starts with a capital letter, e.g., Content-Type, User-Agent, Accept-Encoding. This does not affect the technical processing of headers but makes them easier to read.
56 - Uniqueness: Each header must have a unique name within the context of a single HTTP request or response. It is not permissible to use the same names for different headers in the same request or response.
57 - Special headers: There are headers that are specifically used for controlling caching behavior (Cache-Control), security (Strict-Transport-Security), authentication (Authorization), etc.
58 - RFC Standards: Header requirements are regulated by RFC documents that define standards for Internet protocols. For example, common headers and their use are described in RFC 7231.
Ashterix 23.1 59 {{/spoiler}}
Ashterix 2.1 60
Ashterix 18.1 61
Ashterix 21.2 62
63
Ashterix 25.1 64 == Parameter {{code language="none"}}clients_tokens{{/code}} ==
Ashterix 24.1 65
Ashterix 25.1 66 Now you need to specify an array of client tokens that will have access to the API.
Ashterix 2.1 67
Ashterix 25.1 68 There is some variability here.
Ashterix 2.2 69
Ashterix 25.1 70 === Tokens in parameters ===
Ashterix 2.2 71
Ashterix 5.2 72 (% class="box errormessage" %)
73 (((
Ashterix 25.1 74 **NOT RECOMMENDED!!!**
75 \\This approach is only permissible for local API testing
Ashterix 5.2 76 )))
Ashterix 4.1 77
Ashterix 25.1 78 It is possible to hardcode tokens directly in the settings file.
Ashterix 5.1 79
Ashterix 25.1 80 This is bad from a security standpoint, as if the code is stored in a public repository, everyone will have access to this token.
Ashterix 5.1 81
Ashterix 23.1 82 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
83 ufo_json_rpc:
84 security:
85 protected_methods: ['GET', 'POST']
86 token_key_in_header: 'Ufo-RPC-Token'
87 clients_tokens:
88 - 'ClientTokenExample' # hardcoded token example. Importantly!!! Replace or delete it!
Ashterix 5.3 89
Ashterix 23.1 90 {{/code}}
Ashterix 5.1 91
Ashterix 25.1 92 === Tokens in environment variables ===
Ashterix 5.1 93
Ashterix 25.1 94 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.
Ashterix 5.1 95
Ashterix 25.1 96 In this case, you can write tokens in environment variables (file {{code language="none"}}.env.local{{/code}} during local development). This mechanism is secure enough in terms of access storage.
Ashterix 5.1 97
Ashterix 6.1 98 (((
Ashterix 23.1 99 {{code language="ini" layout="LINENUMBERS" title=".env.local"}}
100 TOKEN_FOR_APP_1=9363074966579432364f8b73b3318f71
101 TOKEN_FOR_APP_2=456fg87g8h98jmnb8675r4445n8up365
102 {{/code}}
Ashterix 6.1 103
Ashterix 23.1 104 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
105 ufo_json_rpc:
106 security:
107 protected_methods: ['GET', 'POST']
108 token_key_in_header: 'Ufo-RPC-Token'
109 clients_tokens:
110 - '%env(resolve:TOKEN_FOR_APP_1)%' # token example from .env.local
111 - '%env(resolve:TOKEN_FOR_APP_2)%' # token example from .env.local
112 {{/code}}
Ashterix 6.1 113 )))
Ashterix 12.2 114
Ashterix 25.1 115 === User Tokens ===
Ashterix 12.2 116
Ashterix 25.1 117 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.
118 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.
Ashterix 13.1 119
Ashterix 25.1 120 In order for the JsonRpcServer to use your logic, you will have to implement your own class that implements the interface {{code language="none"}}Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator{{/code}}
Ashterix 13.3 121
Ashterix 25.1 122 {{code language="php" layout="LINENUMBERS" title="==== Example of a custom token validator ===="}}
Ashterix 23.1 123 <?php
Ashterix 13.1 124
Ashterix 23.1 125 namespace App\Services\RpcSecurity;
Ashterix 13.1 126
Ashterix 23.1 127 use App\Services\UserService;
128 use Symfony\Component\Security\Core\Exception\UserNotFoundException;
129 use Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator;
130 use Ufo\RpcError\RpcInvalidTokenException;
131
132 class UserTokenValidator implements ITokenValidator
Ashterix 13.1 133 {
Ashterix 23.1 134
135 public function __construct(protected UserService $userService) {}
136
137 public function isValid(string $token): true
Ashterix 14.2 138 {
Ashterix 23.1 139 try {
Ashterix 25.1 140 this.userService.getUserByToken(token);
Ashterix 23.1 141 return true;
Ashterix 25.1 142 } catch (UserNotFoundException e) {
143 throw new RpcInvalidTokenException(previous: e);
Ashterix 13.1 144 }
145 }
146 }
Ashterix 23.1 147 {{/code}}
Ashterix 14.2 148
149 (% class="box warningmessage" %)
150 (((
Ashterix 25.1 151 **IMPORTANT!!!**
152 The {{code language="none"}}isValid{{/code}} method must return {{code language="none"}}true{{/code}} if the token exists and is valid, or throw {{code language="none"}}Ufo\RpcError\RpcInvalidTokenException{{/code}} otherwise.
Ashterix 14.2 153 )))
154
Ashterix 25.1 155 After that, you need to specify in the file {{code language="none"}}config/services.yaml{{/code}} that classes dependent on the interface {{code language="none"}}ITokenValidator{{/code}} should accept your new class.
Ashterix 14.2 156
Ashterix 23.1 157 {{code language="yaml" layout="LINENUMBERS" title="config/services.yaml"}}
158 parameters:
159 # some parameters list
160 # ...
Ashterix 14.2 161
Ashterix 23.1 162 services:
163 # some services list
164 # ...
Ashterix 14.2 165
Ashterix 23.1 166 Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator:
167 class: App\Services\RpcSecurity\UserTokenValidator
168 {{/code}}
Ashterix 17.1 169
Ashterix 25.1 170 = Block {{code language="none"}}async{{/code}} =
Ashterix 19.3 171
Ashterix 25.1 172 This block is for configuring [[asynchronous transport>>doc:docs.JsonRpcBundle.functionality.async.WebHome]].
Ashterix 19.3 173
Ashterix 25.1 174 Add the parameter {{code language="none"}}rpc_async{{/code}} which contains a string in DSN format. This string is the configuration for [[Symfony Messenger>>https://symfony.com/doc/current/messenger.html]], and it points to the asynchronous transport where the RPC Server will be waiting for incoming requests if you have a consumer running ({{code language="none"}}php bin/console messenger:consume rpc_async{{/code}}). For a more detailed understanding of this process, read the [[Symfony Messenger documentation>>https://symfony.com/doc/current/messenger.html]].
Ashterix 19.3 175
Ashterix 23.1 176 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
177 ufo_json_rpc:
178 async:
179 rpc_async: '%env(resolve:RPC_TRANSPORT_DSN)%'
Ashterix 19.3 180
Ashterix 23.1 181 {{/code}}
Ashterix 19.3 182
Ashterix 21.2 183 (% class="box warningmessage" %)
184 (((
Ashterix 25.1 185 This configuration implies that you have an environment variable RPC_TRANSPORT_DSN set, which contains the DSN string.
Ashterix 21.2 186 )))
187
Ashterix 25.1 188 = The {{code language="none"}}docs{{/code}} block =
Ashterix 17.1 189
Ashterix 25.1 190 This block configures the generation of documentation when you make a GET request on the RPC Server
Ashterix 17.1 191
Ashterix 25.1 192 == Section {{code language="none"}}response{{/code}} ==
Ashterix 17.1 193
Ashterix 25.1 194 Contains settings that correspond to the content of the response.
Ashterix 17.1 195
Ashterix 25.1 196 === Parameter {{code language="none"}}key_for_methods{{/code}} ===
Ashterix 17.1 197
Ashterix 25.1 198 This parameter allows you to specify the name of the key in the response that will contain an array of available services.
Ashterix 17.1 199
Ashterix 25.1 200 The default value is {{code language="none"}}methods{{/code}}. It can have any string value.
Ashterix 17.1 201
Ashterix 23.1 202 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
203 ufo_json_rpc:
204 docs:
205 key_for_methods: methods
206 {{/code}}
Ashterix 17.1 207
Ashterix 23.1 208 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
209 ufo_json_rpc:
210 docs:
211 key_for_methods: services
212 {{/code}}
Ashterix 17.1 213
Ashterix 23.1 214 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
215 ufo_json_rpc:
216 docs:
217 key_for_methods: some_custom_key
218 {{/code}}
Ashterix 17.1 219
Ashterix 25.1 220 === Parameter {{code language="none"}}async_dsn_info{{/code}} ===
Ashterix 19.4 221
Ashterix 25.1 222 Responsible for displaying information about asynchronous transport in the documentation.
Ashterix 19.4 223
Ashterix 23.1 224 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
225 ufo_json_rpc:
226 async_dsn_info: true # or false
227 {{/code}}
Ashterix 19.4 228
Ashterix 25.1 229 ==== **Documentation Example** ====
Ashterix 19.4 230
Ashterix 23.1 231 {{code language="json" layout="LINENUMBERS" title="GET: /api"}}
Ashterix 19.4 232 {
Ashterix 23.1 233 "envelope": "JSON-RPC-2.0/UFO-RPC-6",
234 "contentType": "application/json",
235 "description": "",
236 "transport": {
237 "sync": {
238 "scheme": "http",
239 "host": "example.com",
240 "path": "/api",
241 "method": "POST"
Ashterix 19.4 242 },
Ashterix 23.1 243 "async": {
244 "scheme": "amqp",
245 "user": "{user}",
246 "pass": "{pass}",
247 "host": "async_rabbit",
248 "port": 5672,
249 "path": "/%2f/json-rpc"
Ashterix 19.4 250 }
251 },
Ashterix 23.1 252 "methods": {
253 ...
Ashterix 19.4 254 }
255 }
Ashterix 23.1 256 {{/code}}
Ashterix 19.4 257
Ashterix 23.1 258 {{info}}
Ashterix 25.1 259 Do not worry about the security of your authorization data contained in the DSN.
Ashterix 19.4 260
Ashterix 25.1 261 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.
Ashterix 19.4 262
Ashterix 25.1 263 The template for protecting data is {{code language="none"}}/([\w\d_]*(?:secret|access|token|key)[_\w]*)=((?:\w|\d)+(?=&?))/{{/code}}.
Ashterix 19.4 264
Ashterix 25.1 265 Example:
Ashterix 19.4 266
Ashterix 23.1 267 {{code language="json" layout="LINENUMBERS" title="RPC_TRANSPORT_DSN=https://sqs.eu-west-3.amazonaws.com/123456789012/messages?access_key=AKIAIOSFODNN7EXAMPLE&secret_key=j17M97ffSVoKI0briFoo9a"}}
Ashterix 19.4 268 {
Ashterix 23.1 269 "async": {
270 "scheme": "https",
271 "host": "sqs.eu-west-3.amazonaws.com",
272 "path": "/123456789012/messages",
273 "query": "access_key={access_key}&secret_key={secret_key}"
Ashterix 19.4 274 }
275 }
Ashterix 23.1 276 {{/code}}
277 {{/info}}
Ashterix 19.4 278
Ashterix 25.1 279 === Parameter {{code language="none"}}validations{{/code}} ===
Ashterix 17.1 280
Ashterix 25.1 281 Responsible for displaying in the documentation methods additional blocks that indicate data validation requirements.
Ashterix 17.1 282
Ashterix 25.1 283 Currently, this block has two possible settings:
Ashterix 17.1 284
Ashterix 23.1 285 * {{code language="none"}}json_schema: <bool>{{/code}}
286 * {{code language="none"}}symfony_asserts: <bool>{{/code}}
Ashterix 17.1 287
Ashterix 25.1 288 In all options in this parameter, the default value is {{code language="none"}}false{{/code}}, meaning these blocks will not appear in the documentation.
289 If you need any of these information blocks when requesting documentation, set the value to {{code language="none"}}true{{/code}}.
Ashterix 17.1 290
Ashterix 23.1 291 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
292 ufo_json_rpc:
293 docs:
294 json_schema: true
295 symfony_asserts: true
296 {{/code}}
Ashterix 17.1 297
Ashterix 25.1 298 ==== **Example of documentation ** ====
Ashterix 17.1 299
300 (% class="box infomessage" %)
301 (((
Ashterix 25.1 302 In this example, I removed the content of the objects symfony_assertions to simplify the example.
303 For more information on method validation, see the page **[[Validate procedures>>doc:docs.JsonRpcBundle.add_rpc_service.assertions.WebHome]]**
Ashterix 17.1 304 )))
305
Ashterix 23.1 306 {{code language="json" layout="LINENUMBERS" title="GET: /api"}}
Ashterix 17.1 307 {
Ashterix 23.1 308 "envelope": "JSON-RPC-2.0/UFO-RPC-6",
Ashterix 25.1 309 "transport": "POST",
310 "target": "/api",
Ashterix 23.1 311 "contentType": "application/json",
312 "description": "",
313 "methods": {
314 "getUserNameByUuid": {
315 "name": "getUserNameByUuid",
316 "description": "Get username by id",
317 "parameters": {
318 "userId": {
319 "type": "string",
320 "name": "userId",
321 "description": "User id in uuid format",
322 "optional": false
Ashterix 17.1 323 }
324 },
Ashterix 23.1 325 "returns": "string",
326 "responseFormat": "string",
327 "json_schema": {
328 "$schema": "http://json-schema.org/draft-07/schema#",
329 "type": "object",
330 "properties": {
331 "userId": {
332 "type": "string"
Ashterix 17.1 333 }
334 },
Ashterix 23.1 335 "required": [
336 "userId"
Ashterix 17.1 337 ]
338 },
Ashterix 23.1 339 "symfony_assertions": {
340 "userId": [
Ashterix 17.1 341 {
Ashterix 23.1 342 "class": "Symfony\\Component\\Validator\\Constraints\\Uuid",
343 "context": {}
Ashterix 17.1 344 }
345 ]
346 }
347 },
Ashterix 23.1 348 "sendEmail": {
349 "name": "sendEmail",
350 "description": "Send mail",
351 "parameters": {
352 "email": {
353 "type": "string",
354 "name": "email",
355 "description": "",
356 "optional": false
Ashterix 17.1 357 },
Ashterix 23.1 358 "subject": {
359 "type": "string",
360 "name": "subject",
361 "description": "",
362 "optional": false
Ashterix 17.1 363 },
Ashterix 23.1 364 "text": {
365 "type": "string",
366 "name": "text",
367 "description": "",
368 "optional": false
Ashterix 17.1 369 }
370 },
Ashterix 23.1 371 "returns": "boolean",
372 "responseFormat": "boolean",
373 "json_schema": {
374 "$schema": "http://json-schema.org/draft-07/schema#",
375 "type": "object",
376 "properties": {
377 "email": {
378 "type": "string",
379 "format": "email"
Ashterix 17.1 380 },
Ashterix 23.1 381 "subject": {
382 "type": "string",
383 "minLength": 1,
384 "maxLength": 100
Ashterix 17.1 385 },
Ashterix 23.1 386 "text": {
387 "type": "string",
388 "minLength": 10
Ashterix 17.1 389 }
390 },
Ashterix 23.1 391 "required": [
392 "email",
393 "subject",
394 "text"
Ashterix 17.1 395 ]
396 },
Ashterix 23.1 397 "symfony_assertions": {
398 "email": [
Ashterix 17.1 399 {
Ashterix 23.1 400 "class": "Symfony\\Component\\Validator\\Constraints\\Email",
401 "context": {}
Ashterix 17.1 402 }
403 ],
Ashterix 23.1 404 "subject": [
Ashterix 17.1 405 {
Ashterix 23.1 406 "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
407 "context": {}
Ashterix 17.1 408 },
409 {
Ashterix 23.1 410 "class": "Symfony\\Component\\Validator\\Constraints\\Length",
411 "context": {}
Ashterix 17.1 412 }
413 ],
Ashterix 23.1 414 "text": [
Ashterix 17.1 415 {
Ashterix 23.1 416 "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
417 "context": {}
Ashterix 17.1 418 },
419 {
Ashterix 23.1 420 "class": "Symfony\\Component\\Validator\\Constraints\\Length",
421 "context": {}
Ashterix 17.1 422 }
423 ]
424 }
425 }
426 }
427 }
Ashterix 23.1 428 {{/code}}