2020-03-11 13:09:25 +03:00
.. meta ::
:description: Action handlers for Hasura actions
:keywords: hasura, docs, actions, handlers
2020-03-11 22:42:36 +03:00
.. _action_handlers:
2020-02-24 19:19:14 +03:00
Action handlers
===============
.. contents :: Table of contents
:backlinks: none
:depth: 1
:local:
Introduction
------------
Actions need to be backed by custom business logic. This business logic can be
defined in a handler which is an HTTP webhook.
HTTP handler
------------
2020-04-16 10:25:19 +03:00
When the action is executed i.e. when the query or the mutation is called, Hasura makes a `` POST `` request to the
handler with the action arguments and the session variables.
2020-02-24 19:19:14 +03:00
The request payload is of the format:
.. code-block :: json
{
2020-04-24 10:55:51 +03:00
"action": {
"name": "<action-name>"
},
2020-02-24 19:19:14 +03:00
"input": {
"arg1": "<value>",
"arg2": "<value>"
},
"session_variables": {
2020-05-21 20:27:11 +03:00
"x-hasura-user-id": "<session-user-id>",
"x-hasura-role": "<session-user-role>"
2020-02-24 19:19:14 +03:00
}
}
2020-06-08 11:07:11 +03:00
.. note ::
All `` session_variables `` in the request payload have lowercase keys.
2020-02-24 19:19:14 +03:00
Returning a success response
----------------------------
To return a success response, you must send back a response payload of action's
response type. The HTTP status code must be `` 2xx `` for a successful response.
Returning an error response
---------------------------
2020-03-03 15:02:40 +03:00
To return an error response, you must send back an error object.
An error object looks like:
2020-02-24 19:19:14 +03:00
.. code-block :: json
{
2020-05-21 20:27:11 +03:00
"message": "<mandatory-error-message>",
2021-09-17 10:43:43 +03:00
"extensions": "<optional-json-object>"
2020-02-24 19:19:14 +03:00
}
2021-09-17 10:43:43 +03:00
where `` extensions `` is an optional JSON value.
If present, `` extensions `` should be a JSON object, which may have a status code
field `` code `` , along with other data you may want to add to your errors:
.. code-block :: json
{
"code": "<optional-error-code>",
"optionalField1": "<custom-data-here>"
}
The HTTP status code must be `` 4xx `` in order to indicate an error response.
For backwards compatibility with previous versions of Hasura, the `` code `` field
may also be supplied at the root of the error object, i.e. at `` $.code `` . This
will be deprecated in a future release, and providing `` code `` within
`` extensions `` is preferred.
2020-02-24 19:19:14 +03:00
Example
-------
For example, consider the following mutation.
.. code-block :: graphql
extend type Mutation {
2021-01-15 15:35:25 +03:00
UserLogin (username: String!, password: String!): UserInfo
2020-02-24 19:19:14 +03:00
}
type UserInfo {
accessToken: String!
userId: Int!
}
Let's say, the following mutation is executed:
2020-04-24 10:55:51 +03:00
.. code-block :: graphql
2020-02-24 19:19:14 +03:00
mutation {
UserLogin (username: "jake", password: "secretpassword") {
accessToken
userId
}
}
Hasura will call the handler with the following payload:
.. code-block :: json
{
2020-05-05 06:46:39 +03:00
"action": {
"name": "UserLogin"
},
2020-02-24 19:19:14 +03:00
"input": {
"username": "jake",
"password": "secretpassword"
},
"session_variables": {
"x-hasura-user-id": "423",
"x-hasura-role": "user"
}
}
To return a success response, you must send the response of the action's output
type (in this case, `` UserInfo `` ) with a status code `` 2xx `` . So a sample
response would be:
.. code-block :: json
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC",
2021-03-04 09:31:02 +03:00
"userId": 423
2020-02-24 19:19:14 +03:00
}
2021-02-17 15:46:53 +03:00
To throw an error, you must send a response of the following type while
2020-02-24 19:19:14 +03:00
setting the status code as `` 4xx `` .
.. code-block :: json
{
"message": "invalid credentials"
}
2020-05-21 20:27:11 +03:00
.. _securing_action_handlers:
2020-07-17 21:39:42 +03:00
Restrict access to your action handler
--------------------------------------
2020-05-21 20:27:11 +03:00
2020-07-17 21:39:42 +03:00
You might want to restrict access to your action handler in order to ensure that it can only get called by your
2020-05-21 20:27:11 +03:00
Hasura instance and not by third parties.
Adding an action secret
^^^^^^^^^^^^^^^^^^^^^^^
2020-07-17 21:39:42 +03:00
One possible way of restricting access to an action handler is by adding a header to the action
2020-05-21 20:27:11 +03:00
that is automatically sent with each request to the webhook, and then adding a check
against that in your action handler.
.. contents ::
:backlinks: none
:depth: 1
:local:
.. note ::
2020-07-17 21:39:42 +03:00
Adding an action secret is a simple way of restricting access to an action handler and will suffice in most use cases.
2020-05-21 20:27:11 +03:00
However, if you have more profound security requirements, you might want to choose advanced
2020-07-17 21:39:42 +03:00
security solutions tailored to your needs.
2020-05-21 20:27:11 +03:00
Step 1: Configure your Hasura instance
***** ***** ***** ***** ***** ***** ***** ***
In your Hasura server, add the action secret as an
environment variable, say `` ACTION_SECRET_ENV `` .
Step 2: Add a header to your action
***** ***** ***** ***** ***** ***** *****
For your action, add a header that will act as an action secret.
.. rst-class :: api_tabs
.. tabs ::
.. tab :: Console
Head to the `` Actions -> [action-name] `` tab in the console and scroll down to `` Headers `` .
You can now configure an action secret by adding a header:
2020-08-25 19:21:21 +03:00
.. thumbnail :: /img/graphql/core/actions/action-secret-header.png
2020-05-21 20:27:11 +03:00
:alt: Console action secret
:width: 75%
Then hit `` Save `` .
.. tab :: CLI
Go to `` metadata/actions.yaml `` in the Hasura project directory.
Update the definition of your action by adding the action secret as a header:
.. code-block :: yaml
:emphasize-lines: 7-9
- actions
- name: actionName
definition:
kind: synchronous
handler: http://localhost:3000
2020-10-13 14:07:46 +03:00
forward_client_headers: true
headers:
- name: ACTION_SECRET
value_from_env: ACTION_SECRET_ENV
2020-05-21 20:27:11 +03:00
Save the changes and run `` hasura metadata apply `` to set the
headers.
2020-10-13 14:07:46 +03:00
.. tab :: API
Headers can be set when creating :ref: `creating <create_action>` or :ref: `updating <update_action>` an action via the metadata API.
.. code-block :: http
:emphasize-lines: 12-17
POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "create_action",
"args": {
"name": "addNumbers",
"definition": {
"kind": "synchronous",
"type": "query",
"headers": [
{
"name": "ACTION_SECRET",
"value_from_env": "ACTION_SECRET_ENV"
}
],
"arguments": [
{
"name": "numbers",
"type": "[Int]!"
}
],
"output_type": "AddResult",
"handler": "https://hasura-actions-demo.glitch.me/addNumbers"
}
}
}
.. note ::
Before creating an action via the :ref: `create_action metadata API <create_action>` , all custom types need to be defined via the :ref: `set_custom_types metadata API <set_custom_types>` .
2020-05-21 20:27:11 +03:00
This secret is only known by Hasura and is passed to your endpoint with every call,
thus making sure only Hasura can successfully authenticate with the action handler.
.. note ::
The name for the action secret is not defined by Hasura and can be chosen freely.
Step 3: Verify the secret in your action handler
***** ***** ***** ***** ***** ***** ***** ***** ***** ***
First, load the action secret as an environment variable in your action handler by adding it to your `` .env `` file
(this file might be a different one depending on your framework).
Second, you need to write some code in your action handler to check that the action secret
passed as a header equals to the one you stored as an environment variable.
The following is an example of a simple authorization middleware with Express:
.. code-block :: javascript
// use authorization for all routes
app.use(authorizationMiddleware);
// authorize action call
function authorizationMiddleware(req, res, next){
if (correctSecretProvided(req)) next();
else res.sendStatus(403);
}
// check if the secret sent in the header equals to the secret stored as an env variable
function correctSecretProvided(req) {
const requiredSecret = process.env.ACTION_SECRET_ENV;
const providedSecret = req.headers['ACTION_SECRET'];
return requiredSecret == providedSecret;
}
2021-03-09 11:36:02 +03:00
.. admonition :: Additional Resources
Introduction to Hasura Actions - `View Recording <https://hasura.io/events/webinar/hasura-actions/?pg=docs&plcmt=body&cta=view-recording&tech=> `__ .