Security and authentication
Learn more about how to secure your client-side applications as they integrate with Knock.
Access to Knock's API is protected using a secret API key for your backend application, and a public API key for your client application.
Public API key endpoints
#By default a public API key within Knock can call the following endpoints on the API (see table below).
Production environments should enable enhanced security mode, which requires clients to send both the public API key and a signed user token that identifies the user that is performing the request. Enhanced security mode trades convenience for security, and we recommend that you enable it when going to production.
Further, it's possible to limit the endpoints that can be called with a public API key by setting access grants on the user token. This can also be used in multi-tenant environments to restrict access to specific tenants that the user has access to.
A public API key can also read and update an object's preferences, but only with a user token that grants access to that object. See scoping a token to an object.
Authentication (without enhanced security)
#In a non-production Knock environment, you can use your public key to authenticate all users. You do not need to implement any other security mechanisms. Knock will not reject requests that do not include a signed user token.
Client SDK example
React notification feed example
Authentication (with enhanced security)
#When enhanced security mode is enabled, Knock will reject requests from the client using your public API key that do not include a signed user token. This token must be generated by your backend application and is used to authenticate a user's requests to Knock using your public API key.
Using our JWT-based authentication approach means using a shared secret to sign a new JWT on your backend. This means you can generate the authentication token out-of-band without an additional network request.
1. Generate the signing key
#You can find the signing key in the Knock dashboard under the Platform > API keys page. Save the private key shown to you here. Note: you won't be shown this key again, so you'll need to regenerate it if you lose access.
The Knock dashboard will present the generated private key in two formats:
- Base-64 encoded PEM format, which fits on a single line (convenient for setting environment variables)
- PEM encoded format, which may be required by certain libraries or platforms (visible under the "Advanced" disclosure)
By convention, we recommend storing the private key in the environment variable KNOCK_SIGNING_KEY. This is where the
Knock SDK will look for the key by default.
2. Sign the JWT
#Within your backend application, you'll need to sign the JWT and make it available to your front-end client. Usually, you'll do this by passing it down as a serialized property on the user or passing in a cookie.
Your JWT will need to be signed against your private signing key using an RS256 algorithm. The JWT must contain the sub claim and we recommend also including iat and exp to enforce token expiration:
To sign your JWT as middleware in a NodeJS express like app:
You can see more details on the options for the signUserToken method in the SDK docs.
3. Send the JWT to the client
#In your client application, you can now use the JWT to authenticate with Knock:
Client SDK example
React notification feed example
Limiting access to specific resources
#You can limit access to the endpoints that can be called with a public API key by setting access grants on the user token.
You can ensure that no other actions can be taken on any other resources by setting the explicit_grants option to true when generating the user token. Without this option, the token will be granted access to all resources by default.
Grants are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource.
Each grant is keyed by the URL of the resource it applies to. A grant for a user uses the user's ID as the key (https://api.knock.app/v1/users/<user_id>), which should match the sub claim in the user token. Grants for tenants and objects use their own keys, covered below.
Scoping a token to a tenant
#Some per-tenant resources can further be scoped to a specific tenant by including a set of grants for one or more tenants in the user token. Doing so will restrict the updates that can be made to the resource to only the tenants that are included in the user token.
Per-tenant resources can be applied to:
Grants for a tenant should always be structured with the tenant's ID as the key (https://api.knock.app/v1/tenants/<tenant_id>). One or more tenant grants can be included in the user token.
Example policies
This policy will only allow the user to read in-app feeds for the tenant acme_corp. Feed reads to any other tenant will be denied, as will feed reads to a nil tenant scope.
To allow the user to read a feed for the unscoped usecase (e.g. tenant = null), and also allow them to read feeds for the tenant acme_corp, you can include a grant the user's resource and a grant for the tenant.
Scoping a token to an object
#A user token can carry grants for a specific object. This enables a client using your public API key to read and update that object's preferences, properties, and channel data, which otherwise requires your secret API key. For example, you can build a settings page where an organization admin chooses which workflows notify the organization's Slack channel, without proxying the requests through your backend.
Object grants can be applied to:
Listing objects in a collection and deleting objects still require your secret API key.
Grants for an object use the object's collection and ID as the key (https://api.knock.app/v1/objects/<collection>/<object_id>). One or more object grants can be included in the user token.
Knock doesn't check how the user in the sub claim relates to the object. Your backend decides which users receive a grant for an object, for example only the admins of the organization the object represents.
Example policy
This policy enables the user to read and update the preferences of the project-123 object in the projects collection.
Handling token expiration
#Generally, it's advisable to set the token expiration to be equal to your session token expiration. That way you can regenerate both of the tokens together from within your application. There may, however, be cases where this is not possible, in which case it's best practice to opt for a relatively short-lived expiration time for your user tokens and refresh them from your backend before the expiration window.
For convenience, the Knock JavaScript client and React package expose an onUserTokenExpiring callback method which will be invoked before the user token expires. This provides an easy hook for your application to refresh the user token from your backend.
At any time your application can call the authenticate method again with an updated user token and the connection to the Knock real-time service will be restarted.
Avoiding authentication
#You can avoid authentication altogether by proxying requests to Knock via your backend, although we don't recommend this approach as it will add more latency for your users.
Troubleshooting
#Errors using your Knock signing key
#If you are getting errors like secretOrPrivateKey must be an asymmetric key when using RS256,
try using the base-64 encoded format of your signing key generated in the Knock Dashboard (under Platform > API keys > Application Signing Keys).