Verify Requests from Space
If your application is supposed to receive requests from Space (for example, a chatbot that listens to requests on some endpoint), it should be able to verify whether the incoming requests are authentic. Space provides a number of verification methods.
(Recommended) Public key
This verification method is based on asymmetric encryption. We recommend using it as the most secure.
Space SDK supports this verification type out of the box. A Space client provides the verifyWithPublicKey()
method that handles the entire signature verification process. To verify the signature, the method requires the request body and the content of the X-Space-Public-Key-Signature
and X-Space-Timestamp
HTTP headers.
For example, this is how you can use this method to verify requests in your application:
The typical workflow looks as follows:
Before sending a request to your application, Space calculates a request hash.
Space generates a request signature: It takes the calculated hash and encrypts it with a private key. The signature is sent along with the request in the
X-Space-Public-Key-Signature
HTTP header. For example, this is a sample Space request including headers:POST /api/myapp HTTP/1.1 X-Forwarded-For : ::ffff:123.123.123.123 X-Forwarded-Proto : https X-PageKite-Port : 443 X-Space-Timestamp : 1632844347462 X-Space-Public-Key-Signature : iOjFEi5EpW+FbU1CJl+oc0QOJbIrv7kaV/VTEMa5i0ot6418N3ObQGz2C0tB8e2N4vHKQ7LWMZZ+OJexeHHoJGvL0XN7nwWb5k7Hn1DOPMMkWgSi6kL5orkyOHqIDPaHgZzX6IGbl3LP/aPms7E1NXaFRIfxuUQGdsDeA7yCjTIv/Rq8AbV9VqVI5TudAhHmaxh6R19wgtzWxaud27rsXV8INAvwXAQLMf5Ld+E1Mzi67qrS79Jxa7U34SJjC9xDe1wYMCZGc+G+6L4zdqVB08C2MCfI1IVYzqiHLyJgCHxhBmvjJ8JivoqadbRKOrbKShvSzHqFD5geEBovO0HqKA== User-Agent : Space (81383) Ktor http-client Accept-Charset : UTF-8 Accept : */* Content-Length : 196 Content-Type : application/json Host: 12345abcdef.ngrok.io Connection : Keep-Alive {"className":"ListCommandsPayload","clientId":"f6df3d26-d9fc-41c5-9fbd-0e7896f2cfb0","userId":"2BgVYn24Jx6u"}The application receives the request. Now, it should verify the request signature.
First, the application must obtain a public key from Space. To do this, the application must send an HTTP request to the
applications/public-keys
Space endpoint. For instance, this is how a request might look like:GET https://mycompany.jetbrains.space/api/http/applications/clientId:abc1234/public-keys Authorization: Bearer abc1234 Accept: application/jsonHere
clientId
is the client ID assigned to your application during registration. API Playground can help you with generating a request:In the reply, Space sends a JSON Web Key set. Typically, the set contains only one key. When a current public key becomes outdated, there is a period of time when the set contains two keys (the current one and a new one).
For each key in the set, the application must:
Get the value of the
X-Space-Timestamp
header and the request body.Generate a string consisting of the timestamp and the body. Use colon
:
as a delimiter. For example in Kotlin, it can look like follows:val str = "$timestamp:$body"In case of the sample request above, the
str
value would be:1632844347462:{"className":"ListCommandsPayload", "clientId":"f6df3d26-d9fc-41c5-9fbd-0e7896f2cfb0","userId":"2BgVYn24Jx6u"}Calculate string hash using SHA512.
Decrypt the content of the
X-Space-Public-Key-Signature
header using the RSA algorithm.Compare the decrypted signature with the calculated hash. If they are equal, the application is allowed to process the request, if not, the application must return the HTTP
401 Unauthorized
response code.Note that only one key from the set must return the correct hash result.
Important: After a certain period of time, public keys become outdated. So, while the application can cache public keys (to avoid requesting them from Space each time), the cache should be cleared once the cached keys don't produce a correct signature. Space SDK does such caching under the hood.
Sample code
The following Kotlin code snippet demonstrates how to check the signature in the X-Space-Public-Key-Signature
header:
Signing key
The idea of this method is that Space uses a special signing key to generate a hash for every request it sends to your application. The calculated hash is sent in the X-Space-Signature
HTTP header. In turn, your application has to use the same signing key to calculate a hash of a received request. Then it should compare the calculated hash value with the hash value in the request.
There are no helper functions in Space SDK for this method, so, our task is to implement the verification logic described in Verify Requests from Space. To calculate hash, you can use the Apache Commons Codec library. To reference it from a Gradle project, add the following lines to build.gradle
:
To
repositories
:repositories { jcenter() // ... other repos }To
dependencies
:dependencies { compile group: 'commons-codec', name: 'commons-codec', version: '1.15' // ... other dependencies }
This is how a simple implementation of this method can look like:
The typical verification workflow looks as follows:
During registration of your application, Space can issue it a signing key. To get the key, you should open the Endpoint tab of the application settings and click Generate under Signing key.
Save the signing key in your application, for example, as a string constant.
Before Space sends a request to your application, it calculates the request hash using the generated signing key and puts it into the
X-Space-Signature
header. For example, this is a sample Space request including headers:POST /api/myapp HTTP/1.1 Host: 12345abcdef.ngrok.io User-Agent: Space (61355) Ktor http-client Content-Length: 163 Accept: */* Accept-Charset: UTF-8 Content-Type: application/json X-Forwarded-For: 123.456.123.456 X-Forwarded-Proto: https X-Space-Signature: 2aa8cba6217a28686de0ca8dcfe2a1d0795e343d744a0c5307308e43777593a5 X-Space-Timestamp: 1607623492912 Accept-Encoding: gzip {"className":"ListCommandsPayload","accessToken":"","verificationToken":"d415ca5965b37f4f0cac59fd33de7b94e396284e897d0fb8a070d0a5e1b7f2d3","userId":"2kawvQ4F6GM6"}Now, it's the application's turn to calculate the request hash. To do this:
Get the value of the
X-Space-Timestamp
header and the request body.Generate a string consisting of the timestamp and the body. Use colon
:
as a delimiter. For example in Kotlin, it can look like follows:val str = "$timestamp:$body"In case of the sample request above, the
str
value would be:1607623492912:{"className":"ListCommandsPayload","accessToken":"","verificationToken":"d415ca5965b37f4f0cac59fd33de7b94e396284e897d0fb8a070d0a5e1b7f2d3","userId":"2kawvQ4F6GM6"}Use HMAC SHA256 to hash the string.
Compare the calculated hash with the one you get from the
X-Space-Signature
header. If they are equal, the application is allowed to process the request, if not, the application must return the HTTP401 Unauthorized
response code.
(Obsolete) Verification token
The idea behind the method is to compare the verification token in the request body with the request your application obtained during registration in Space. As the verification token is a part of the payload, the SDK provides an extension method for the ApplicationPayload
class:
true
if verificationToken
is equal to the token in the payload.This is how a simple implementation of this method can look like:
The typical verification workflow looks as follows:
During registration of your application, Space can issue it a verification token. To get the token, you should open the Endpoint tab of the application settings and click Generate under Verification token.
Save this token in your application, for example, as a string constant.
When Space sends a request to your application, it puts this verification token in the request body. For example, this is how the body of a slash command request looks like (a user presses / in the chatbot's channel):
{ "className": "ListCommandsPayload", "accessToken": "", "verificationToken": "d415ca5965b37f4f0cac59fd33de7b94e396284e897d0fb8a070d0a5e1b7f2d3", "userId": "2kawvQ4F6GM6" }The task of the application is to get the
verificationToken
from the request payload and compare it to the token stored in the application. If they are equal, the application is allowed to process the request, if not, the application must return the HTTP401 Unauthorized
response code.
SSL client certificate
This verification method implies that all requests from Space to the application are encrypted with an SSL client key. The verification is handled not by the application but by the web server that hosts the application. Generally, to configure this verification method, you should:
Generate an SSL keystore file with private and public keys.
Upload the SSL keystore file to Space.
During application registration, choose the uploaded keystore.
On the web server that hosts your application, configure SSL client certificate authentication. For the exact instructions, refer to the web server official documentation.
HTTP authentication
This method implies using the standard HTTP authentication based on the Authorization
request header. There are two ways to perform verification: using a bearer token and using basic authentication by providing a username and a password.
Space SDK doesn't provide any helper methods to perform this verification method. Most of the modern frameworks provide support for HTTP authentication out of the box. For example, here you can find the instructions for Ktor.
Bearer token
During registration of your application, select HTTP Authentication and then Bearer.
In Token, specify a verification token.
When sending a request, Space will add this token to the
Authorization
header. For example:POST /api/myapp HTTP/1.1 Host: 12345abcdef.ngrok.io User-Agent: Space (61355) Ktor http-client X-Space-Timestamp: 1624376380652 Authorization: Bearer abc1234 X-Space-DeliveryID: e90ffc27-87dc-43f3-a13a-ac3860d53770 Accept-Charset: UTF-8 Accept: /The task of the application is to get the
Authorization: Bearer
header value and compare it to the token stored in the application. If they are equal, the application is allowed to process the request, if not, the application must return the HTTP401 Unauthorized
response code.
Basic authentication
During registration of your application, select HTTP Authentication and then Basic.
Specify a Username and a Password.
Space will use the specified credentials to create a single string:
username:password
(a colon:
is used as a separator). Then it will encode the string using the Base64 encoding.When sending a request, Space will add the generated string to the
Authorization
header. For example, forjohndoe:pwd1234
:POST /api/myapp HTTP/1.1 Host: 12345abcdef.ngrok.io User-Agent: Space (61355) Ktor http-client X-Space-Timestamp: 1624449426984 Authorization: Basic am9obmRvZTpwd2QxMjM0 X-Space-DeliveryID: 2cd74c76-9cbb-4da5-81ab-7ce578145ccc Accept-Charset: UTF-8 Accept: /The task of the application is to get the
Authorization: Basic
header value, decode it back to string and compare it to the credentials stored in the application. If they are equal, the application is allowed to process the request, if not, the application must return the HTTP401 Unauthorized
response code.