JetBrains Space Help

Verify Requests from Space

For the sake of security, all requests coming from Space must be checked for authenticity. Space provides a number of verification methods.

This topic provides you instructions on how to implement request verification in your application using the Space SDK:

For general instructions on verification methods, refer to this topic.

(Recommended) Verifying requests using a public key

We recommend using verification with a public key instead of other methods as it is more secure.

This verification method is based on asymmetric encryption. For every request sent to your application, Space creates a signature: It calculates a request hash and encrypts it using a private key. The application should verify the signature: calculate the hash as well, decrypt the signature received from Space, and compare two hashes. To decrypt the signature, the application must use a public key obtained from Space. Learn more

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:

import io.ktor.client.* import space.jetbrains.api.runtime.SpaceHttpClient import space.jetbrains.api.runtime.helpers.verifyWithPublicKey import space.jetbrains.api.runtime.withServiceAccountTokenSource import io.ktor.application.* import io.ktor.http.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.* // other imports private val url = "https://mycompany.jetbrains.space/" private val clientId = "client-id-assigned-to-app-during-registration" private val clientSecret = "client-secret-issued-to-app-during-registration" val spaceClient by lazy { SpaceHttpClient(HttpClient(CIO)) .withServiceAccountTokenSource( clientId = clientId, clientSecret = clientSecret, serverUrl = url ) } suspend fun verifyRequestWithPublicKey(body: String, signature: String, timestamp: String): Boolean { // verify the request using a public key return spaceClient.verifyWithPublicKey(body, timestamp.toLong(), signature) } fun Routing.backToSpace() { post("/api/myapp") { // get request body val body = call.receiveText() // get hash calculated by Space val signature = call.request.header("X-Space-Public-Key-Signature") val timestamp = call.request.header("X-Space-Timestamp") val verified = signature != null && timestamp != null && verifyRequestWithPublicKey(body, signature, timestamp) if (!verified) { call.respond(HttpStatusCode.Unauthorized) return@post } // here goes your code } }

Verifying requests using a signing key

The idea of this method is that Space uses a special signing key to calculate hash for every request it sends to your application. The application should calculate the hash as well and compare it to the hash 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:

import org.apache.commons.codec.digest.HmacAlgorithms import org.apache.commons.codec.digest.HmacUtils // ... other imports // signing key issued during app registration val signingKey = "abc123" // calculate hash and compare it to hash from request fun verifyPayloadWithSigningKey(body: String, signature: String, timestamp: String) : Boolean { val checkedSignature = HmacUtils(HmacAlgorithms.HMAC_SHA_256, signingKey). hmacHex("$timestamp:$body") return signature == checkedSignature } fun Routing.backToSpace() { // the endpoint that handles Space requests post("api/myapp") { // get payload as text val body = call.receiveText() // get timestamp and message hash calculated by Space val signature = call.request.header("X-Space-Signature") val timestamp = call.request.header("X-Space-Timestamp") // check hash val verified = signature != null && timestamp != null && verifyPayloadWithSigningKey(body, signature, timestamp) if (!verified) { call.respond(HttpStatusCode.Unauthorized) return@post } // ... } }

(Obsolete) Verifying requests using a 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:

ApplicationPayload.verifyWithToken(verificationToken: String): Boolean
The method returns true if verificationToken is equal to the token in the payload.

This is how a simple implementation of this method can look like:

// token issued during app registration val verificationToken = "abc123" fun verifyPayload(payload: ApplicationPayload): Boolean { return payload.verifyWithToken(verificationToken) } fun Routing.backToSpace() { // the endpoint that handles Space requests post("api/myapp") { // get payload as text val body = call.receiveText() // deserialize payload into ApplicationPayload val payload = readPayload(body).also { if (!verifyPayload(it)) { call.respond(HttpStatusCode.Unauthorized) return@post } } // ... } }
Last modified: 02 June 2022