Skip to main content

Service accounts

A service account lets an application send authenticated requests to the Webmarketer API.

To send requests to the Webmarketer API with a service account, you have to:

  1. Create a service account and a key for that account from the web interface
Private key

The key attached to a service account must be kept in a secure place

  1. Generate, in your application, a JWT signed with the service account's private key
  2. Exchange that JWT for an API access token with the Webmarketer OAuth server
  3. Send the retrieved access token to the API with your requests

Service account key

When you create a key for a service account from the web interface, it is downloaded to your machine as a JSON file.

The file looks like this:

{
"clientId": <unique identifier>,
"serviceAccountEmail": <service account email>,
"privateKeyId": <key identifier>,
"privateKey": <private key in PEM format>
}

That information is needed to generate a JWT and retrieve an API access token from the Webmarketer OAuth server.

Generating a JWT

A JSON Web Token is made of 3 parts: the header, the payload and the signature. The JWT header and payload are JSON objects.

The JWT header expected by the OAuth server must contain the following properties:

  • kid, holding the key identifier (privateKeyId in the key file)
  • alg, which defines the algorithm used to sign the JWT; the only allowed algorithm is RS256
  • typ, holding the value JWT
{
"kid": <privateKeyId>,
"alg": "RS256",
"typ": "JWT"
}

The JWT payload must contain the following properties:

  • iss, holding the issuer of the JWT (clientId in the key file)
  • sub, holding the subject the JWT is issued for (serviceAccountEmail)
  • aud, holding the intended audience of the JWT — the URL of the OAuth server route used to retrieve an API access token
  • scope, holding a list of scopes requested for the API access token
  • exp, giving the date until which the JWT must be considered valid, as a timestamp
{
"iss": <clientId>,
"sub": <serviceAccountEmail>,
"aud": "https://oauth.webmarketer.io/oidc/token",
"scope": "full_access",
"exp": <some number>
}

Once the JWT header and payload are generated, they have to be encoded in base64url (base64 where the + and / characters are replaced by - and _ respectively, and where the = characters have been removed).

The first part of the JWT is obtained by concatenating the encoded header, a dot . and the encoded payload.

The last part is obtained by signing that first part with RSA SHA-256 and the private key held in the key file (privateKey).

Once the signature is generated, the final JWT is obtained by concatenating the first part, a dot . and the base64url-encoded signature.

The generated JWT is made of 3 parts separated by dots:

eyJraWQiOiJwcml2YXRlS2V5SWQiLCJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnRJZCIsInN1YiI6InNlcnZpY2VBY2NvdW50RW1haWwiLCJhdWQiOiJodHRwczovL29hdXRoLndlYm1hcmtldGVyLmlvL29pZGMvdG9rZW4iLCJzY29wZSI6ImZ1bGxfYWNjZXNzIiwiZXhwIjoxNjI5MzA0NTY0fQ.OZBSymD_ho5OZjQUHHV_2__ctU9Gk7QxHTW-9LwiafsilR8NYwW0GR56S6zlhw-YpEj28wz75KwsiJI4shO2WYkbc3jtGSTqbc7Mw69clfA4XQ0cjxHVUrVKfVgIiX8bAwocIOSdvJhRJTcpmeN7SVjtKp79kFmfRESBcp3YTv_6QOyuFL-hABDqJYF92x0fzV14b-9AsulIb_JZggrKYEgylLPDkvauL68zI34hcV-lyVXG3A-Al5yEUo3qFzEknG-RHrjD4QmtzwttR_fLMj-1s8dlg8oSJtIruzCy8jp9eIAJjJKmLW8zK0KQEqRz-rg7rQwWMIxw_ESP7HMSaA

Examples

import { sign } from "crypto";
import { readFile } from "fs/promises";

function base64urlEncode(buffer: Buffer): string {
return buffer.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}

async function getKey(keyFilePath: string) {
// retrieve the key
const jsonEncodedKey = await readFile(keyFilePath, "utf-8");
const key = JSON.parse(jsonEncodedKey);
return key;
}

function generateJwt(key: Record<string, any>): string {
// generate the header
const header = {
kid: key.privateKeyId,
alg: "RS256",
typ: "JWT",
};

// generate the payload
const payload = {
iss: key.clientId,
sub: key.serviceAccountEmail,
aud: "https://oauth.webmarketer.io/oidc/token",
scope: "full_access",
exp: Date.now() / 1000 + 300, // 5 minutes
};

// first part of the jwt
let jwt = base64urlEncode(Buffer.from(JSON.stringify(header)))
+ "."
+ base64urlEncode(Buffer.from(JSON.stringify(payload)));

// generate the signature
const signature = sign("RSA-SHA256", Buffer.from(jwt), key.privateKey);

// complete jwt
jwt = jwt
+ "."
+ base64urlEncode(signature);

return jwt;
}

Getting an API access token

Service accounts negotiate API access tokens with the OAuth server using the jwt-bearer grant type.

Simply send a POST request to the /oidc/token route with the following parameters in the request body:

  • grant_type must hold the value urn:ietf:params:oauth:grant-type:jwt-bearer
  • client_id matches the clientId property of the key
  • assertion must hold the JWT generated earlier
Content-Type

The request body must be in the application/x-www-form-urlencoded format.

The server returns a response of the form:

{
"access_token": "eyJraWQiOiJwcml2YXRlS2V5SWQiLCJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJ1bmlxdWVJZCIsInN1YiI6ImFjY291bnRJZCIsImlhdCI6MTYyOTM4ODg5MCwiZXhwIjoxNjI5MzkyNDkwLCJzY29wZSI6ImZ1bGxfYWNjZXNzIiwiaXNzIjoiaHR0cHM6Ly9vYXV0aC53ZWJtYXJrZXRlci5pbyIsImF1ZCI6ImNsaWVudElkIn0.b1BZJdE-5OKNvRKdM8uEPWVwbDHoxu74p_A6NK1KuQW2hQmKcKDLUNBRqTDpw156P9ll9DORxbBdRKrY988WdvQNMs_ehSgIHdfM-W02EHDxgSXIpyNgj5th76XibP-6Glhvhbo24-ZOFdK7V1EBVYrxcLTviunqw42JrBf59W2OsvOYIuEzDOY3jH8oQ-s20PKoCqq_g5HBcjH_9-eoFLJnwgaiTwtSbuayJiGUJjkjmki1dp_2YRORXFY0VKzsCrd6b4URgYTt6M9o-61WCsmgYiqD--1hZ55hfg4WphSIhlJgSvPl4tqn0_wITPDcgLNRHaT1MDD1m42py5_rDA",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "full_access"
}

Examples

curl --request POST \
--url https://oauth.webmarketer.io/oidc/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
--data client_id=<clientId> \
--data assertion=<JWT>

Sending requests to the API

To send an authenticated request to the API, simply add the Authorization header with the value Bearer <jwt>.