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.
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.
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 (typenumber, notstring). It sets the token’s validity period and helps prevent unauthorized access.
Publicly Viewable Data:
- The
idandusernamefields 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.
- The
Authentication Flow and Token Use
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.
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:
- Admin Tokens: These are signed similarly but are used solely for backend interactions and system-level actions. User-specific details like
idshould not be validated when processing requests with an admin 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
- Token Expiry: Always set an expiry date on tokens (
expclaim) to ensure sessions are time-limited. - 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.
- 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.
- 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
- Token Expiry Check: Your application will check if the token is close to expiring. If so, it will call the
refreshTokenfunction. - Server Call: This function makes a POST request to your server to obtain a new token.
- 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.