Authentication and JWT Tokens

Our platform uses JWT (JSON Web Token) authentication to manage user sessions and secure server-to-server interactions. JWTs are digitally signed tokens containing encoded information, allowing systems to verify their authenticity without needing to store session data. Here’s a brief overview and guide for implementing JWT-based authentication.

What is a JWT?

A JWT is a self-contained token that includes encoded information (claims) about the user or system. Each JWT is digitally signed with a secret key using the HS256 algorithm, allowing the server to verify the token’s integrity. In our system, the signature ensures that the token has not been tampered with and was generated by an authorized source.

Token Setup and Security

The JWT secret must always be kept on the server side and should never be exposed to the front-end or any public domain, as leaking this key would allow anyone to forge valid tokens, compromising the security of your application.

  1. Secrets for Signing:

    • For secure production use, we will provide a secret key to generate the JWT. This key will be shared securely.
    • We also provide a staging secret for testing purposes, which is safer to share for development.
  2. Token Payload Requirements:

    • The payload is the core of the JWT, containing essential information about the user. For a valid session token, your payload must include:

      
          
      { "id": "user's unique ID in your system", "username": "public username or handle", "exp": "expiry timestamp as a number (Unix time)" }
    • Example Payload:

      
          
      { "id": "12345", "username": "john_doe", "exp": 1700000000 }
      • id: Unique identifier for the user in your system (non-sensitive).
      • username: Publicly viewable username or handle for the user (non-sensitive).
      • exp: Expiry timestamp, here set to 1700000000 (Unix time), which represents an expiration date.
    • Token Expiry (exp): This field should be a Unix timestamp (type number, not string). It sets the token’s validity period and helps prevent unauthorized access.

  3. Publicly Viewable Data:

    • The id and username fields will be viewable in the front-end. Ensure these fields contain non-sensitive, displayable data. If you lack a username field in your system, contact your Scout PAM or Project Lead to discuss possible solutions Scout can provide and host.

Authentication Flow and Token Use

  1. Token Generation and Use:

    • You will generate the JWT on your server and pass it to our front-end script when the game loads. Once the token is available, our front-end will treat the state as a logged-in user.
    • Our servers will validate the token’s signature, and if valid, it establishes the user’s session.
  2. User Session Verification:

    • If additional session verification is desired, you may include relevant session data in the token. This token can then be validated again in the create-bet callback, ensuring the user session is secure.

Server-to-Server Authentication

For server-to-server callbacks, such as refunds and settling bets, we use a system-signed admin token. This token is generated by our system and should be treated as an administrator-level token:

Implementation Examples

Below are examples of a server-side endpoint that generates a session token for the currently logged-in user. Every example signs the token with the HS256 algorithm, includes the required id, username, exp and iat claims, sets a 1 hour expiry, and reads the secret from server-side configuration. Replace the user lookup with however your system resolves the authenticated user, and remember to use the staging secret for testing and the production secret only in production. The first comment in each example shows how to install the JWT library it uses.


    
// npm install jsonwebtoken const jwt = require("jsonwebtoken"); app.get("/api/scout-token", (req, res) => { const user = req.user; // the authenticated user in your system const now = Math.floor(Date.now() / 1000); const token = jwt.sign( { id: String(user.id), username: user.username, exp: now + 60 * 60, iat: now, }, process.env.SCOUT_JWT_SECRET ); res.send(token); });

    
# pip install pyjwt flask import os import time import jwt from flask import Flask app = Flask(__name__) @app.get("/api/scout-token") def scout_token(): user = get_authenticated_user() # the authenticated user in your system now = int(time.time()) payload = { "id": str(user.id), "username": user.username, "exp": now + 60 * 60, "iat": now, } return jwt.encode(payload, os.environ["SCOUT_JWT_SECRET"], algorithm="HS256")

    
# pip install pyjwt fastapi import os import time import jwt from fastapi import Depends, FastAPI from fastapi.responses import PlainTextResponse app = FastAPI() @app.get("/api/scout-token", response_class=PlainTextResponse) def scout_token(user=Depends(get_authenticated_user)): now = int(time.time()) payload = { "id": str(user.id), "username": user.username, "exp": now + 60 * 60, "iat": now, } return jwt.encode(payload, os.environ["SCOUT_JWT_SECRET"], algorithm="HS256")

    
<?php // composer require firebase/php-jwt require __DIR__ . '/vendor/autoload.php'; use Firebase\JWT\JWT; $user = getAuthenticatedUser(); // the authenticated user in your system $now = time(); $payload = [ 'id' => (string) $user->id, 'username' => $user->username, 'exp' => $now + 60 * 60, 'iat' => $now, ]; header('Content-Type: text/plain'); echo JWT::encode($payload, getenv('SCOUT_JWT_SECRET'), 'HS256');

    
// dotnet add package System.IdentityModel.Tokens.Jwt using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; app.MapGet("/api/scout-token", (HttpContext context, IConfiguration config) => { var user = GetAuthenticatedUser(context); // the authenticated user in your system var descriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id), new Claim("username", user.Username), }), IssuedAt = DateTime.UtcNow, Expires = DateTime.UtcNow.AddHours(1), SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Scout:JwtSecret"])), SecurityAlgorithms.HmacSha256), }; var handler = new JwtSecurityTokenHandler(); return Results.Text(handler.WriteToken(handler.CreateToken(descriptor))); });

    
// implementation("com.auth0:java-jwt") — Spring Boot import java.time.Instant; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ScoutTokenController { @GetMapping("/api/scout-token") public String scoutToken() { User user = getAuthenticatedUser(); // the authenticated user in your system Instant now = Instant.now(); return JWT.create() .withClaim("id", String.valueOf(user.getId())) .withClaim("username", user.getUsername()) .withExpiresAt(now.plusSeconds(60 * 60)) .withIssuedAt(now) .sign(Algorithm.HMAC256(System.getenv("SCOUT_JWT_SECRET"))); } }

    
// implementation("com.auth0:java-jwt") — Ktor import java.time.Instant import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import io.ktor.server.response.respondText import io.ktor.server.routing.Route import io.ktor.server.routing.get fun Route.scoutToken() { get("/api/scout-token") { val user = call.getAuthenticatedUser() // the authenticated user in your system val now = Instant.now() val token = JWT.create() .withClaim("id", user.id.toString()) .withClaim("username", user.username) .withExpiresAt(now.plusSeconds(60 * 60)) .withIssuedAt(now) .sign(Algorithm.HMAC256(System.getenv("SCOUT_JWT_SECRET"))) call.respondText(token) } }

Security Best Practices

  1. Token Expiry: Always set an expiry date on tokens (exp claim) to ensure sessions are time-limited.
  2. Signature Verification: Our platform will verify the token’s signature on each request. Verifying the signature on your end ensures that each token is valid and untampered.
  3. Separate Tokens for Staging and Production: Only use the staging secret for development. Production environments should always use the production secret shared securely by Scout.
  4. Store the secret securely: The JWT secret must always be kept on the server side and should never be exposed to the front-end or any public domain, as leaking this key would allow anyone to forge valid tokens, compromising the security of your application.

By following these steps, JWT tokens provide a secure, efficient means of user authentication and session management on our platform.

Here’s the section on Refreshing the Token on User Activity:


Updating the Token to Prevent Expiry (Front end)

To ensure that user sessions remain active, it’s important to refresh the JWT token when user activity is detected near the token’s expiration. By updating the token periodically, you prevent it from going stale and avoid unnecessary logouts or session interruptions.

Exposing a Refresh Function

If you expose a refresh function to our front-end script, we can trigger it whenever the token is near expiry. This function should handle all necessary steps to obtain a fresh token, then return the new token back to our script.

Example Refresh Function in JavaScript

Here’s a simple example of how you might implement this function on the client side, using JavaScript:


    
window.refreshToken = async function () { try { // Example call to your server's endpoint to get a fresh token const response = await fetch('https://your-server.com/api/refresh-token', { method: 'POST', credentials: 'include', // Include cookies if needed for authentication headers: { 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error('Token refresh failed'); const data = await response.json(); return data.token; // Assuming the new token is in data.token } catch (error) { console.error('Error refreshing token:', error); return null; // Handle failure, possibly triggering a logout } };

How It Works

  1. Token Expiry Check: Your application will check if the token is close to expiring. If so, it will call the refreshToken function.
  2. Server Call: This function makes a POST request to your server to obtain a new token.
  3. Return Token: The function returns the new token to the front-end. New token should be assigned as following: ftwWidgets.config.token = {newToken}

Implementing a refresh flow like this ensures user sessions remain active and reduces friction, enhancing the overall user experience.