Prisma Server

Authentication & Security

Overview

A Prisma server has two components that need to be secured in production environments:

  • The Management API of the Prisma server
  • One or more Pisma services that are running on the Prisma server

A Prisma server provides the runtime environment for one or more Prisma services. To create, delete and modify the Prisma services on a Prisma server, the Management API is used. The Management API is protected with the Management API secret specified in the Docker Compose files when the Prisma server is deployed. Learn more here.

Prisma services are secured via the service secret that's specified in your prisma.yml. A Prisma service typically serves application data that's stored in relation to a certain datamodel. Learn more here.

Prisma server

Management API secret

To ensure only entitled users are able to perform actions through the Management API, it needs to be protected with a secret. This secret is called Management API secret.

The Management API secret is set in the Docker Compose file you're using to configure your Prisma server. It is part of the PRISMA_CONFIG environment variable and specified via the managementApiSecret key:

Copy
Unless `managementApiSecret` is defined in the Docker configuration, everyone with access to the URL of the Prisma server will be able to make changes, including deploying and deleting Prisma services as well as modifying data.

After setting managementApiSecret, you need to run docker-compose up -d to recreate the containers. After that, you'll need to use the environment variable PRISMA_MANAGEMENT_SECRET to authenticate with the Management API from the Prisma CLI, learn more here.

Management API token

Service tokens follow the JSON Web Token (JWT) specification (RFC 7519):

"JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted."

A JWT has the following three components:

  • Header: The header typically consists of two parts: the type of the token, which is JWT, and the hashing algorithm being used (which is HS256 in the case of Prisma service tokens).

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  • Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. Here is what it looks like when granting permissions for modifying a service called demo deployed to the dev stage:

    {
      "grants": [
        {
          "target": "demo/dev",
          "action": "*"
        }
      ],
      "iat": 1532956915,
      "exp": 1690744915
    }
    
  • Signature: The signature is used to verify the message wasn't changed along the way. To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that. For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)
    

Therefore, a JWT typically looks like this: xxxxx.yyyyy.zzzzz

Read more about JWTs here.

Claims

The JWT must contain the following claims:

  • Issued at: The iat field contains a Unix timestamp with the exact time when the token was generated.
  • Expiration date: The exp field containts a Unix timestamp denoting the expiration date of the token.
  • Grants: The grants field is an object with two keys

    • The target field specifies the name and stage of the service that's modified with a certain action. Use the */* wildcard to allows actions for all services running on the Prisma server.
    • The action field specifies which actions are allowed against the target. The wildcard * allows all actions.

Here is the sample payload of a JWT using wildcards to allow all actions on all services:

  {
    "grants": [
      {
        "target": "*/*",
        "action": "*"
      }
    ],
    "iat": 1532956915,
    "exp": 1690744915
  }

Authenticating against the Management API

There are two ways to use the Management API:

  • Use the Prisma CLI which then performs the actual API requests
  • Send requests directly to the /management path of your Prisma server

Using the Prisma CLI

When using the Prisma CLI to perform requests against the Management API (e.g. using prisma deploy), you don't need to worry about generating Management API tokens. The CLI generates them for you upon every API request. Therefore, the CLI needs to know the Management API secret of your Prisma server in order to generate a Management API token and authenticate its requests.

This is why you have to set the PRISMA_MANAGEMENT_API_SECRET environment variable when using the Prisma CLI.

The CLI reads the secret from the PRISMA_MANAGEMENT_API_SECRET environment variable and uses it to generate the JWT which it then attaches to the API request.

Depending on your shell, the syntax for setting environment variables might differ. The standard Unix shell uses the following syntax:

export PRISMA_MANAGEMENT_API_SECRET="my-server-secret-42"

Sending requests directly

When you want to send HTTP requests directly to the Management API of your Prisma server, you need to generate a JWT and attach it to the Authorization field of the HTTP header (prefixed with Bearer and a whitespace).

The Prisma CLI has a hidden helper command for generating Management API tokens: prisma cluster-token. Before running it, you need to set the PRISMA_MANAGEMENT_API_SECRET environment variable. Note that this command is not officially documented and might change without further notice.

There already is a pending feature request for a similar command.

Troubleshooting

FAQ

Where do I find my Management API secret?

The Management API secret is specified in your Docker Compose file (via the managementApiSecret key) at the time when your Prisma server is being deployed. If you don't know the secret or don't have access to the Docker Compose file, you need to ask the administrator of your Prisma server who was responsible for the deployment.

If you have used the Prisma Cloud Heroku integration to deploy your Prisma server, you can find out how to reveal the Management API secret here.

Common errors

Error: "Server at __URL__ requires the Management API secret. Please set the the PRISMA_MANAGEMENT_API_SECRET environment variable."

This error means that your server is protected with the Management API secret. Whenever you access the server and need to authenticate against the Management API, you must provide a JWT that was generated based on the secret. When using the Prisma CLI, you don't need to generate the JWT manually, instead this will be done by the Prisma CLI itself. To do so, it needs to be aware of the Management API secret which it can only read from the PRISMA_MANAGEMENT_API_SECRET environment variable.

If your Management API secret is mysecret42, you can set the it in your terminal with the following command:

export PRISMA_MANAGEMENT_API_SECRET="mysecret42"

Prisma services

A Prisma API can be protected with the service secret (specified as the secret property) in your prisma.yml:

secret: my-secret-42

As the developer of a Prisma service, you can choose your own service secret. When a service gets deployed with a prisma.yml that contains the secret property, the Prisma API of that service will require authentication via a service token (JWT):

The easiest way to obtain a service token is by running the prisma token command inside the same directory where your prisma.yml is located:

$ prisma token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJkZW1vQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MzI1ODgzNzAsImV4cCI6MTUzMzE5MzE3MH0.Nv8coqsiwdwoSfWCBJHYfnr0WK2GRyqO5xTN6Q3IVkw

The generated token needs to be attached to the Authorization header of the HTTP requests made to the Prisma API. Note that it needs to prefixed with the token type (and a separating space character), which in this case Bearer because it's a bearer token:

curl '__YOUR_PRISMA_ENDPOINT__' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer __YOUR_SERVICE_TOKEN__' \
--data-binary '{"query":"mutation { createUser(data: { name: "Sarah" }) { id } }"'

The service secret and service tokens are not to be confused with the Prisma Management API secret used to protect the Management API of your Prisma server.

Service secret

The service secret is an alphanumeric random string specified by the developer of a Prisma service. It set as the secret property in your service's prisma.yml:

secret: my-secret-42

You can also specify it via an environment variable. For example, if you have an environment variable called MY_SECRET, you can set it in your prisma.yml like so:

secret: ${env:MY_SECRET}

When running prisma generate or prisma deploy, the Prisma CLI will read the MY_SECRET environment variable. Learn more about using environment variables in prisma.yml here.

Also note that prisma generate automatically injects the secret into the generated Prisma client. So there's no need to generate a service token explicitly. Learn more generating the Prisma client here.

Service token

Service tokens follow the JSON Web Token (JWT) specification (RFC 7519):

"JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted."

A JWT has the following three components:

  • Header: The header typically consists of two parts: the type of the token, which is JWT, and the hashing algorithm being used (which is HS256 in the case of Prisma service tokens).

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
  • Payload: The payload contains the claims. Claims are statements about an entity (typically, the user) and additional data. Here is what it looks like for a service called demo deployed to the dev stage:

    {
      "data": {
        "service": "demo@dev",
        "roles": [
          "admin"
        ]
      },
      "iat": 1532530208,
      "exp": 1533135008
    }
    
  • Signature: The signature is used to verify the message wasn't changed along the way. To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that. For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)
    

Therefore, a JWT typically looks like this: xxxxx.yyyyy.zzzzz

Read more about JWTs here.

Claims

The JWT must contain the following claims:

  • Issued at: The iat field contains a Unix timestamp with the exact time when the token was generated.
  • Expiration date: The exp field containts a Unix timestamp denoting the expiration date of the token. Service tokens are valid exactly one week.
  • Service information: The data field is an object with two keys

    • The service field specifies the name and stage of the service
    • The roles field contains the access permissions granted with that token. In the future there might be support for more fine grained access control by introducing a concept of roles such as ["write:Log", "read:*"]

Here is the sample Payload of a JWT:

{
  "data": {
    "service": "myservice@prod",
    "roles": [
      "admin"
    ]
  },
  "iat": 1532530208,
  "exp": 1533135008
}

Sending the service token

The service token is a bearer token:

Bearer Token: A security token with the property that any party in possession of the token (a "bearer") can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).

It needs to be sent to the Prisma API as specified by the OAuth 2.0 Authorization Framework specification.

When sending the access token in the Authorization request header field defined by HTTP/1.1 [RFC2617], the client uses the Bearer authentication scheme to transmit the access token.

For example:

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

The syntax of the Authorization header field for this scheme follows the usage of the Basic scheme defined in Section 2 of [RFC2617]. Note that, as with Basic, it does not conform to the generic syntax defined in Section 1.2 of [RFC2617] but is compatible with the general authentication framework being developed for HTTP 1.1 [HTTP‑AUTH], although it does not follow the preferred practice outlined therein in order to reflect existing deployments. The syntax for Bearer credentials is as follows:

b64token    = 1*( ALPHA / DIGIT /
                   "-" / "." / "_" / "~" / "+" / "/" ) *"="
credentials = "Bearer" 1*SP b64token
Clients SHOULD make authenticated requests with a bearer token using the `Authorization` request header field with the `Bearer` HTTP authorization scheme. Resource servers MUST support this method.

Example: Protecting your Prisma API

Here is an example how you can protect your Prisma API and make authenticated requests against it.

An unprotected Prisma API

Assume you're starting out with a new Prisma service being configured through the following service configuration:

datamodel.prisma

type User {
  id: ID! @unique
  name: String!
}

prisma.yml

endpoint: https://eu1.prisma.sh/john-doe/demo/dev # deployed to a Prisma Demo server 
datamodel: datamodel.prisma

For this example, assume the service gets deployed to a Demo server and therefore is available on the public internet.

When the service gets deployed with the above service configuration, everyone who knows the service's endpoint is able to send queries and mutations to its API, meaning they effectively can perform arbitrary reads and writes against your database:

curl 'https://eu1.prisma.sh/jane-doe/demo/dev' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"mutation { createUser(data: { name: "Sarah" }) { id }}"}'

When using a GraphQL Playground, no HTTP headers need to be set in order to talk to the Prisma API.

Protecting the Prisma API

The only thing you need to do in order for your Prisma API to require authentication is setting the service secret in prisma.yml:

endpoint: https://eu1.prisma.sh/john-doe/demo/dev # deployed to a Prisma Demo server 
datamodel: datamodel.prisma

secret: my-secret-42

To apply this change, you also need to redeploy the service:

prisma deploy

If you're now trying to resend the above HTTP request using curl:

curl 'https://eu1.prisma.sh/jane-doe/demo/dev' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"mutation { createUser(data: { name: "Sarah" }) { id }}"}'

You will receive the following error:

{
  "errors" : [ {
    "message" : "Your token is invalid. It might have expired or you might be using a token from a different project.",
    "code" : 3015,
    "requestId" : "eu1:api:cjk28t8e55tld0b296sr2ey6v"
  } ]
}

This will happen for all requests made against the Prisma API, no matter if it's a query, mutation or subscription that's requested. When no HTTP headers are set in the GraphQL Playground, requests will fail with the same error message:

To fix this, you need to include a service token in the Authorization header field of the HTTP request. You can obtain a service token using the Prisma CLI:

prisma token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJkZW1vQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MzI1MzAyMDgsImV4cCI6MTUzMzEzNTAwOH0.FM6haUilhi89-c-2h7asV3-Ot6NQrs1qoaKL-wPjj04

The token that's printed by the CLI needs to be set in the Authorization header field and prefixed with Bearer:

curl 'https://eu1.prisma.sh/jane-doe/demo/dev' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJkZW1vQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MzI1MzAyMDgsImV4cCI6MTUzMzEzNTAwOH0.FM6haUilhi89-c-2h7asV3-Ot6NQrs1qoaKL-wPjj04' \
--data-binary '{"query":"mutation { createUser(data: { name: "Sarah" }) { id }}"}'

Similarly, in a GraphQL Playground you need to set it (in JSON format) in the HTTP HEADERS pane in the bottom-left corner:

{
  "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InNlcnZpY2UiOiJkZW1vQGRldiIsInJvbGVzIjpbImFkbWluIl19LCJpYXQiOjE1MzI1MzAyMDgsImV4cCI6MTUzMzEzNTAwOH0.FM6haUilhi89-c-2h7asV3-Ot6NQrs1qoaKL-wPjj04M"
}

If you're opening the Playground using the prisma playground command, the Prisma CLI automatically injects the Authorization header with a valid service token.