Authenticating with GV1

This page describes the GV1 protocol with is the internal protocol used to authenticate requests to the API.

The GV1 protocol is used mostly by internal tools, such as the web interface and client tools, to establish authorized sessions on your behalf.

Getting Started

The following steps should be performed once per process.

  1. Generate a device key, on first use. If there is no existing device key, generate it. A device key is an ECDSA P-256 key pair. This is the which will uniquely identify your device to the API. Store this key securely on the device.

  2. Generate a session key. Each time you connect, you’ll also need an ECDSA P-256 to use as a session key. You should not store this key to disk.

  3. Fetch the tenant configuration. Each tenant with has a unique configuration. You can determine your configuration by fetching https://SIGNIN_URL/config, where SIGNIN_URL is a virtual host configured for your tenant.

    $ curl https://SIGNIN_URL/config
    tenant: 5xyyocliasebyh

    Constructing a request

    1. Determine which headers will be signed

    You don’t need to sign all the headers, but any HTTP headers you include that are not part of the signed headers will be ignored. Add your semicolon-delimited list of headers to the request as X-Grooveid-SignedHeaders. You must include X-Grooveid-Tenant and either X-Grooveid-Date or Date.

    X-Grooveid-SignedHeaders: Accept;Content-Type;User-Agent;X-Grooveid-Date;X-Grooveid-Tenant
  4. Compute the string to sign

    The string to sign consists of the following fields, separated by newline characters:

    • The server’s host name, e.g.
    • The tenant ID, e.g. 5xyyocliasebyh
    • The request method, e.g. POST
    • The path part of the URL, e.g. /users
    • The query string, e.g. start=10&limit=100
    • The hex-encoded SHA-256 hash of the canonical header string.

    The canonical header string is computed by concatenating:

    • each of the signed headers, each terminated by \r\n
    • The hex-encoded SHA-256 hash of the request body.

    For example: Accept: application/json\r\nContent-Type: application/json\r\nb5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

  5. Compute the authorization header

The authorization header consists of the string gv1, a space, and a URL encoded string consisting of:

- `dev=DEVICE_PUBLIC_KEY` - The device public key, base64 encoded using the URL-safe alphabet and with trailing equals-sign characters stripped.
- `sig=SIGNATURE` - The ECDSA signature of the _string to sign_ using the device key.
- `ses=SESSION_PUBLIC_KEY` - The session public key, base64 encoded using the URL-safe alphabet and with trailing equals-sign characters stripped.
- `mac=MAC` - the HMAC SHA-256 of SIGNATURE using the _session shared secret_, if a session has been established. If the session secret has not yet been established, such as in the first request of a session, then omit this parameter.
  1. Derive session shared secret

    If you’ve just generated a new session key, you will have to derive the session secret before you can make other requests.

    You may also need to generate a session shared secret if your request is rejected with a response header containing X-Error-Code: Invalid Session. If this happens, discard the session key and shared secret, regenerate them, and retry the request.

    To generate a shared secret, make a HEAD request to

    HEAD / HTTP/1.0
    X-Grooveid-Date: Mon, 02 Jan 2006 15:04:05 GMT
    X-Grooveid-Tenant: TENANT
    X-Grooveid-Signed-Headers: X-Grooveid-Date;X-Grooveid-Tenant

    The response will contain an X-Grooveid-Session-Init header which contains a URL-safe base64-encoded ECDSA public key on the P-256 curve, the server public key.

    Use the server public key and the session key to perform Elliptic-curve Diffie–Hellman (ECDH) to derive the shared secret. Use the shared secret to compute the mac parameter in the Authorization header.

    Your init request might look like:

    HEAD / HTTP/1.1
    Authorization: gv1 dev=BLhLAwTdTjN196LHydRmMqeZANw_-0pbQRhkxlvdvOTscX4jgPsspnC0lANeHkFUNV1Yq_izYBDzl0RRdmtJ4aE&sig=vp7darrJhKOqRW4J9WDy-bnvZeCe-sLhdov6O_kDkmLIqOHLzp0pRPzABY4gRTDWEVfABsYe2mvmKc5O01cjaA&ses=BKoLpJ1b22sHao4ODdIhRaKXU6G6eb6et90a4rpwD96OgE2RCbEVRkRPQgSAGOrPd43xjg2-5gSxf4orGHzEvx4
    x-grooveid-date: Mon, 10 Dec 2018 21:07:23 GMT
    x-grooveid-signedheaders: X-Grooveid-Date;X-Grooveid-Tenant
    x-grooveid-tenant: 5xyyocliasebyh

    And the response might look like:

    HTTP/1.1 401 Unauthorized
    date: Mon, 10 Dec 2018 21:07:23 GMT
    expires: -1
    x-content-type-options: nosniff
    x-grooveid-session-init: BD6KGb387jywwcPjbaa0lTQSXZgnqRmmkNvmEy72nkbEqM44jrlWJDRr1nP5dgvSvI0Y_6ZzLs-SjHXoiN1GUvY

    Use the session secret to compute the mac parameter in subsequent requests.

    1. Putting it all together

    You can now make authenticated requests to the API, which might look like:

    GET /tenant HTTP/1.1
    Authorization: gv1 dev=BLhLAwTdTjN196LHydRmMqeZANw_-0pbQRhkxlvdvOTscX4jgPsspnC0lANeHkFUNV1Yq_izYBDzl0RRdmtJ4aE&sig=4TDwIs0Ff8BZ0IXy63-X8vOc2EL0b1M4kFYsEJKXRmeQk6P6HXNhHT2eXpiYnZXuq1P0z3VO-7lCvtq59ZPSHQ&ses=BKoLpJ1b22sHao4ODdIhRaKXU6G6eb6et90a4rpwD96OgE2RCbEVRkRPQgSAGOrPd43xjg2-5gSxf4orGHzEvx4&mac=NxiGKLIC3bZs6OOWak6EC6HTyeQq0_VRsQ_C_dp_2Xc
    x-grooveid-date: Mon, 10 Dec 2018 21:07:23 GMT
    x-grooveid-signedheaders: X-Grooveid-Date;X-Grooveid-Tenant
    x-grooveid-tenant: 5xyyocliasebyh

    The server will respond with 200 Ok and a JSON-encoded tenant object.

Last modified May 12, 2020: refactor docs (d7a7a5c1d)