Authorization

Last updated

GraphQL Access Token#

An access token is a credential that is bound to certain set of permissions. The set of permissions is decided during token generation. It is not bound to any specific user by the application, but it might be issued with specific user in mind.

Access token has an obligatory expiration time after which it will no longer authorize any requests.

Obtaining Access Token via AMS#

For GraphQL access you need user token with correct permissions. This could be done in the backend AMS. Navigate to System -> Api Tokens and then add a new token by clicking + Integration API TOKEN button.

Here we are able to provide restrictions, select permissions, and expiration time. Requirements for generating token are:

  • Providing description (it is a good practice to provide a description that allows unambiguous token identification)
  • At least one permission

Expiration time is optional - the default value equals 30 days.

Token Revocation#

Access tokens can be revoked in the AMS when necessary. Navigate to System -> Api Tokens and select the token that you want to invalidate. This is the moment when good practice of naming tokens unambiguously pays off. When token details are displayed use the X Revoke button.

Authorizing Requests#

One way to authorize the request is to provide an Authorization header:

1 2 3 POST *base*/graphql Authorization: Bearer <access token>

CURL example:

1 2 3 4 5 curl "${BASE_URL}/graphql" \ -X POST \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"query":"{ __schema { types { name } } }"}'

Another way to authorize request to GraphGL API is to add a cookie named graphql-access with only the access token as value.

1 2 3 POST *base*/graphql Cookie: graphql-access=<access token>

CURL example:

1 2 3 4 5 curl "${BASE_URL}/graphql" \ -X POST \ -H "Cookie: graphql-access=${ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"query":"{ __schema { types { name } } }"}'

For instructions on how to attach a header or cookie in your API client refer to the client's documentation.

Permissions#

The list of permissions is changing as new permissions are added to match new queries and mutations.

During your integration's initial tests in QA, it's worth to note all the information returned in extensions.permissionsUsed, so that you know exactly which permissions are required for use cases covered in your integration. This way you can later use API tokens with minimal permissions when you move to Production, which we highly recommend. Running Production integrations on tokens with full admin permissions is considered bad practice, and a potential security vulnerability.

If you call the API without required permissions, you will be informed about this explicitly:

Request:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { orderConnection( last: 10, before: "bnVtYmVyOjE2Ng==", where: {storeType: WHOLESALE} ) { totalCount pageInfo{hasPreviousPage, hasNextPage, startCursor, endCursor} edges{ node{ number status grandTotal{ value currency {code} } orderDate } cursor } } }

Response:

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 { "errors": [ { "message": "You need Order:read permission to access orderConnection.", "extensions": { "category": "authorization" }, "locations": [ { "line": 6, "column": 3 } ], "path": [ "orderConnection" ] } ], "extensions": { "complexity": 50, "permissionsUsed": [ "Order:read" ] } }

Deprecations in the latest GQL API versions#

We're making some changes to our GraphQL Integration API because we want it to reflect business concepts better and need to align the naming.

We are doing our best not to introduce breaking changes so that existing queries still work, but you can switch to using new names at any time. For example, when a field is renamed, a new field is added, and the old one still works. When a returned type changes, the old type is turned into an interface so that fragments explicitly specifying types are not broken.

Moreover, we are now returning a list of deprecated fields used under extensions, so you can see exactly whether your queries use deprecated fields and when they will be deleted.

Example response:

1 2 3 4 5 6 7 8 9 10 11 12 { "data": { ... }, "extensions": { "deprecatedFieldsUsed": [ "Field: Query.displays, reason: Use ObjectWithTranslations instead of Localizable, date of removal: 2023-09-04", "Field: Display.localized, reason: Renamed localized to translations, date of removal: 2023-09-04", "Field: LanguageTranslation.translations, reason: Renamed to fields, date of removal: 2023-09-04" ], } }

User warnings - not required, but important!#

GraphQL mutations return HTTP 200 response, and the way issues are communicated is through the userErrors field in payloads. However, not all issues are equal, and some non-critical ones actually don't prevent mutations from succeeding.

This is especially important for batch actions like price updates (setPrices), where it is really important to save all other prices rather than failing because of one that is wrong for some trivial reason, e.g.:

  • "Duplicate product ID 123 skipped"
  • "Duplicate variant ID 456 skipped"
  • "Product variant with id 789 not assigned to product 123"
  • "Product ID 345 is a bundle with dynamic price type, changing its prices has no effect, skipped"

Sometimes warnings are purely informative, like "Weight unit has been changed from KILOGRAMS to POUNDS", or "Weight has been rounded to 3 decimal places".

userErrors from now on contain only errors, and userWarnings - all other issues. They both have a message and a path.

1 2 3 4 5 6 7 8 9 10 11 12 mutation updateProductWeight { updateProduct(id: 1, input: { weight: { value: 11 unit: POUNDS } }) { product { id, weight { formattedValue } } userErrors { message path } userWarnings { message path } # NEW! } }

It makes sense to add userWarnings to all mutations, even if you don’t expect anything like mentioned above. New warnings can be added in the future.