Skip to content

Authorizing Requests to Widget GraphQL API

Overview

This document details the technical specifications for authorizing access to the Widget API. The authorization process utilizes JSON Web Tokens (JWT) as the primary mechanism for granting and managing access. Partners are provisioned with secret ID-key pairs, which they use to issue JWTs to their users for specific sport matches.

Authorization

To authorize access to the Widget API, you must provide the JWT as the token attribute of the widget component.

JWT Structure

JWTs used for authorization adhere to the standard JWT structure and consist of three parts:

  1. Header: Contains information about the token type (JWT) and the signing algorithm used (HS256).
  2. Payload: Contains the claims, which are statements about an entity (typically, the user) and additional data.
  3. Signature: A string created by encrypting the header and payload with the secret key, used to verify the token's authenticity.

The header consists of two parts:

  • typ (string) - the type of the token;
  • alg (string) - the algorithm used to sign the token.

Payload

The payload must contain the following claims. Adding additional ones will not affect the token's validity.

  • key_id (string) - Unique identifier of the Partner's secret key used to sign the JWT;
  • exp (number) - Standard claim that defines the expiration time of the token. The value is a Unix timestamp representing the number of seconds since the Unix epoch;
  • event_id (string) - Unique identifier for the specific sports event for which the token is valid;
  • ip (string) - IPv4 address of the user to whom the token was issued;
  • sub (string) - User identifier, allowing the Partner to identify the specific user associated with the token. Must be unique for each Partner's user, but not necessarily an internal identifier (consider symmetric encryption, hashing or using external ids).

JWT Example

Provided we have the following secret:

id key
cpb2jhmcgi1ngarccrmg J08PuNVKmF8El2zxFIBRydQU2K0rQi6z

Here is the payload we want to include into the token:

Key Value
event_id 30c1d59e-49eb-42cf-bb6a-6684aa92d4c8
ip 1.2.3.4
sub abcdef123456

We want to issue a token valid until Wed May 01 2024 05:00:00 GMT+0000.

Here is how the Header and Payload would look (raw):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Header:
{
    "typ": "JWT",
    "alg": "HS256"
}

// Payload:
{
    "key_id": "cpb2jhmcgi1ngarccrmg",
    "exp": 1714539600,
    "event_id": "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8",
    "ip": "1.2.3.4",
    "sub": "abcdef123456"
}

As per the JWT standard, we need to sign the token with the secret key. The signature is calculated as follows:

1
2
3
4
signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
);

(encode both the header and payload in Base64, combine them with a dot, and sign the result with the secret key)

The final JWT must look like this:

1
base64UrlEncode(header).base64UrlEncode(payload).base64UrlEncode(signature)

In this example, the token would have this value (signed with secret key J08PuNVKmF8El2zxFIBRydQU2K0rQi6z):

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXktaWQiOiJjcGIyamhtY2dpMW5nYXJjY3JtZyIsImV4cCI6MTcxNDUzOTYwMCwiZXZlbnQtaWQiOiIzMGMxZDU5ZS00OWViLTQyY2YtYmI2YS02Njg0YWE5MmQ0YzgiLCJpcCI6IjEuMi4zLjQiLCJzdWIiOiJhYmNkZWYxMjM0NTYifQ.H-Hb-yWI7uKDHCsGORUmYyes8bExwBm82vg7gTUGThk

Examples of Implementation

Most of the time, developers won't need to implement the JWT signing algorithm from scratch. Instead, they can use libraries that provide this functionality.

Here are some examples of how to generate JWTs in different programming languages:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
    "fmt"
    "time"

    "github.com/golang-jwt/jwt/v5"
)

func generateToken() (string, error) {
    secretKeyID := "cpb2jhmcgi1ngarccrmg"
    secretKey := "J08PuNVKmF8El2zxFIBRydQU2K0rQi6z"

    exp := time.Now().Add(24 * time.Hour).Unix()

    claims := jwt.MapClaims{
        "key_id":   secretKeyID,
        "exp":      exp,
        "event_id": "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8",
        "ip":       "1.2.3.4",
        "sub":      "abcdef123456",
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    token, err := token.SignedString([]byte(secretKey))
    if err != nil {
        return "", err
    }

    return token, nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const jwt = require("jsonwebtoken");

function generateToken() {
  const secretKeyID = "cpb2jhmcgi1ngarccrmg";
  const secretKey = "J08PuNVKmF8El2zxFIBRydQU2K0rQi6z";

  const exp = Math.floor(Date.now() / 1000) + 60 * 60; // 1 hour

  const payload = {
    keyId: secretKeyID,
    exp,
    eventId: "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8",
    ip: "1.2.3.4",
    sub: "abcdef123456",
  };

  return jwt.sign(payload, secretKey, { algorithm: "HS256" });
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import jwt
import datetime

def generate_token():
    secret_key_id = "cpb2jhmcgi1ngarccrmg"
    secret_key = "J08PuNVKmF8El2zxFIBRydQU2K0rQi6z"

    expiration_time = datetime.datetime.utcnow() + datetime.timedelta(hours=24)
    exp = int(expiration_time.timestamp())

    payload = {
        "key_id": secret_key_id,
        "exp": exp,
        "event_id": "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8",
        "ip": "1.2.3.4",
        "sub": "abcdef123456",
    }

    token = jwt.encode(payload, secret_key, algorithm="HS256")

    return token
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;

public class JwtGenerator
{
    public static string GenerateToken()
    {
        var secretKeyId = "cpb2jhmcgi1ngarccrmg";
        var secretKey = "J08PuNVKmF8El2zxFIBRydQU2K0rQi6z";

        var expirationTime = DateTime.UtcNow.AddHours(24); // Expiration in 24 hours

        var claims = new[]
        {
            new Claim("key_id", secretKeyId),
            new Claim("exp", ((DateTimeOffset)expirationTime).ToUnixTimeSeconds().ToString()),
            new Claim("event_id", "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8"),
            new Claim("ip", "1.2.3.4"),
            new Claim("sub", "abcdef123456"),
        };

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = expirationTime,
            SigningCredentials = credentials
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(token);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;

public class JwtGenerator {
    public static String generateToken() {
        String secretKeyId = "cpb2jhmcgi1ngarccrmg";
        String secretKey = "J08PuNVKmF8El2zxFIBRydQU2K0rQi6z";

        Key key = Keys.hmacShaKeyFor(secretKey.getBytes()); // Generate signing key

        Date expirationTime = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000); // 24 hours

        return Jwts.builder()
                .claim("key_id", secretKeyId)
                .claim("exp", expirationTime.getTime() / 1000) // Convert to seconds
                .claim("event_id", "30c1d59e-49eb-42cf-bb6a-6684aa92d4c8")
                .claim("ip", "1.2.3.4")
                .claim("sub", "abcdef123456")
                .signWith(key)
                .compact();
    }
}