BowlingTracker.com SSO Integration Guide
This guide explains how to integrate your site or forum with BowlingTracker.com using
JSON Web Tokens (JWT) signed with RSA-256. When a user clicks a link to BowlingTracker.com
from your site, your server generates a short-lived signed JWT and appends it to the URL
as a Token query-string parameter (or passes it in an
Authorization: Bearer <token> HTTP header).
- Your assigned Issuer string (e.g.
https://yoursite.com/forums) - Your RSA-2048 private key (PEM format, kept secret on your server)
- Paid integrations can display your logo.
BowlingTracker.com stores only your public key for signature verification. We can generate a private key for you, if needed.
How It Works
- User clicks a BowlingTracker.com link on your site.
-
Your server builds a JWT containing the user's identity claims, signs it with your RSA private key, and redirects the browser to:
https://bowlingtracker.com/Home/Index?Token=<jwt> - BowlingTracker.com validates the signature using your registered public key, reads the claims, and logs the user in automatically.
Required JWT Claims
| Claim | Description | Example |
|---|---|---|
iss | Issuer — your assigned identifier string | https://yoursite.com/forums |
aud | Audience — must be exactly this value | https://bowlingtracker.com |
nbf | Not Before — recommend 15 minutes in the past to allow clock skew | Unix timestamp |
exp | Expiry — token must expire within 5–15 minutes of issue | Unix timestamp |
ExternalId | Unique, stable user ID on your system | 42 |
Username | Display username on your system | JohnDoe |
vBulletin 5.x Integration
vBulletin does not ship with a JWT library, so install one via Composer. The example below uses firebase/php-jwt, which is the most widely used PHP JWT library.
1. Install the library
Run this in the root of your vBulletin installation (where composer.json lives,
or create one):
composer require firebase/php-jwt
2. Store your private key
Save your RSA private key PEM file outside your web root, e.g.
/etc/ssl/private/bt_private.pem, and make it readable only by your
web-server user. Never commit it to source control.
3. Create a vBulletin plugin or custom page
In vBulletin Admin → Plugin & Product Manager, add a new plugin hooked to
global_start (or create a custom PHP page in
/includes/). The snippet below can be dropped into either location.
<?php
// File: includes/bt_sso_redirect.php
// Hook: global_start (or call directly from a custom navigation link)
require_once DIR . '/vendor/autoload.php';
use Firebase\JWT\JWT;
// ── Configuration ────────────────────────────────────────────────
define('BT_ISSUER', 'https://yoursite.com/forums'); // Your assigned issuer
define('BT_AUDIENCE', 'https://bowlingtracker.com');
define('BT_PRIVATE_KEY', file_get_contents('/etc/ssl/private/bt_private.pem'));
define('BT_REDIRECT', 'https://bowlingtracker.com/Home/Index');
// ─────────────────────────────────────────────────────────────────
// Only proceed if a vBulletin user is logged in.
if (!isset($vbulletin->userinfo['userid']) || $vbulletin->userinfo['userid'] == 0) {
// Not logged in — send them to BowlingTracker without a token.
header('Location: ' . BT_REDIRECT);
exit;
}
$user = $vbulletin->userinfo;
$now = time();
$payload = [
'iss' => BT_ISSUER,
'aud' => BT_AUDIENCE,
'nbf' => $now - 900, // 15 min clock-skew buffer
'exp' => $now + 300, // 5 min expiry
'ExternalId' => (string)$user['userid'],
'Username' => $user['username'],
];
$jwt = JWT::encode($payload, BT_PRIVATE_KEY, 'RS256');
header('Location: ' . BT_REDIRECT . '?Token=' . urlencode($jwt));
exit;
4. Add a navigation link
In vBulletin Admin → Navigation Manager, add a new link pointing to
/includes/bt_sso_redirect.php (or whatever path you chose).
Users who are already logged in to your forum will be silently authenticated
on BowlingTracker.com.
$vbulletin object the same way. Composer's autoload path
may differ depending on where you ran composer install.
Simple Machines Forum (SMF 2.x) Integration
SMF uses a hook system. Install firebase/php-jwt via Composer, then register a hook to intercept navigation actions.
1. Install the library
composer require firebase/php-jwt
2. Create an SMF mod / hook file
Create Sources/BowlingTrackerSSO.php in your SMF installation:
<?php
// File: Sources/BowlingTrackerSSO.php
if (!defined('SMF')) die('No direct access...');
require_once(dirname __DIR__ . '/vendor/autoload.php');
use Firebase\JWT\JWT;
// ── Configuration ────────────────────────────────────────────────
define('BT_ISSUER', 'https://yoursite.com/forums');
define('BT_AUDIENCE', 'https://bowlingtracker.com');
define('BT_PRIVATE_KEY', file_get_contents('/etc/ssl/private/bt_private.pem'));
define('BT_REDIRECT', 'https://bowlingtracker.com/Home/Index');
// ─────────────────────────────────────────────────────────────────
/**
* Called from a custom SMF action, e.g. ?action=bt_sso
* Register in Modifications.php:
* $modSettings['integrate_actions'] .= ',bt_sso=BowlingTrackerSSO_Action';
*/
function BowlingTrackerSSO_Action()
{
global $user_info;
if ($user_info['is_guest']) {
// Guest — redirect without token.
redirectexit(BT_REDIRECT);
}
$now = time();
$payload = [
'iss' => BT_ISSUER,
'aud' => BT_AUDIENCE,
'nbf' => $now - 900,
'exp' => $now + 300,
'ExternalId' => (string)$user_info['id'],
'Username' => $user_info['name'],
];
$jwt = JWT::encode($payload, BT_PRIVATE_KEY, 'RS256');
redirectexit(BT_REDIRECT . '?Token=' . urlencode($jwt));
}
3. Register the action hook
In your Settings.php or a mod's install.php, add:
add_integration_function('integrate_actions', 'BowlingTrackerSSO_Action', false, '$sourcedir/BowlingTrackerSSO.php');
4. Add a menu item
Link your users to https://yoursite.com/forums/index.php?action=bt_sso.
SMF will invoke the hook, generate the token, and redirect seamlessly.
$modSettings['integrate_actions'] string-append style instead of
add_integration_function().
Generic PHP Integration
Works for any PHP 7.4+ application (Laravel, Symfony, CodeIgniter, plain PHP, etc.).
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
$privateKey = file_get_contents('/etc/ssl/private/bt_private.pem');
$now = time();
$payload = [
'iss' => 'https://yoursite.com/forums',
'aud' => 'https://bowlingtracker.com',
'nbf' => $now - 900,
'exp' => $now + 300,
'ExternalId' => (string)$_SESSION['user_id'],
'Username' => $_SESSION['username'],
];
$jwt = JWT::encode($payload, $privateKey, 'RS256');
header('Location: https://bowlingtracker.com/Home/Index?Token=' . urlencode($jwt));
exit;
Node.js / Express Integration
// npm install jsonwebtoken
const jwt = require('jsonwebtoken');
const fs = require('fs');
const privateKey = fs.readFileSync('/etc/ssl/private/bt_private.pem');
// Express route: GET /bt-sso
app.get('/bt-sso', (req, res) => {
if (!req.user) return res.redirect('https://bowlingtracker.com/Home/Index');
const token = jwt.sign(
{
ExternalId: String(req.user.id),
Username: req.user.username,
},
privateKey,
{
algorithm : 'RS256',
issuer : 'https://yoursite.com/forums',
audience : 'https://bowlingtracker.com',
notBefore : '-15m',
expiresIn : '5m',
}
);
res.redirect(`https://bowlingtracker.com/Home/Index?Token=${encodeURIComponent(token)}`);
});
Python / Django / Flask Integration
# pip install PyJWT cryptography
import jwt, time
from cryptography.hazmat.primitives import serialization
with open('/etc/ssl/private/bt_private.pem', 'rb') as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
now = int(time.time())
payload = {
'iss': 'https://yoursite.com/forums',
'aud': 'https://bowlingtracker.com',
'nbf': now - 900,
'exp': now + 300,
'ExternalId': str(request.user.id),
'Username': request.user.username,
}
token = jwt.encode(payload, private_key, algorithm='RS256')
# Flask example
from flask import redirect
return redirect(f'https://bowlingtracker.com/Home/Index?Token={token}')
ASP.NET Core Integration
Install Microsoft.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt via NuGet.
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
// In your controller action:
public IActionResult BowlingTrackerSSO()
{
if (!User.Identity?.IsAuthenticated ?? true)
return Redirect("https://bowlingtracker.com/Home/Index");
var pem = System.IO.File.ReadAllText("/etc/ssl/private/bt_private.pem");
var rsa = RSA.Create();
rsa.ImportFromPem(pem);
var creds = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
var claims = new[]
{
new Claim("ExternalId", User.FindFirstValue(ClaimTypes.NameIdentifier)!),
new Claim("Username", User.Identity!.Name!),
};
var token = new JwtSecurityToken(
issuer: "https://yoursite.com/forums",
audience: "https://bowlingtracker.com",
claims: claims,
notBefore: DateTime.UtcNow.AddMinutes(-15),
expires: DateTime.UtcNow.AddMinutes(5),
signingCredentials: creds);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return Redirect($"https://bowlingtracker.com/Home/Index?Token={Uri.EscapeDataString(tokenString)}");
}
WordPress Integration
Install firebase/php-jwt via Composer, then add a shortcode or a custom page template.
<?php
// Add to your theme's functions.php or a custom plugin.
require_once get_template_directory() . '/vendor/autoload.php';
use Firebase\JWT\JWT;
add_action('template_redirect', function () {
if (!is_page('bowling-tracker-sso')) return; // Only on a dedicated page slug.
if (!is_user_logged_in()) {
wp_redirect('https://bowlingtracker.com/Home/Index');
exit;
}
$user = wp_get_current_user();
$now = time();
$privateKey = file_get_contents('/etc/ssl/private/bt_private.pem');
$payload = [
'iss' => 'https://yoursite.com',
'aud' => 'https://bowlingtracker.com',
'nbf' => $now - 900,
'exp' => $now + 300,
'ExternalId' => (string)$user->ID,
'Username' => $user->user_login,
];
$jwt = JWT::encode($payload, $privateKey, 'RS256');
wp_redirect('https://bowlingtracker.com/Home/Index?Token=' . urlencode($jwt));
exit;
});
XenForo 2.x Integration
Create a simple XenForo add-on with a controller action.
<?php
// File: src/addons/YourVendor/BtSso/Pub/Controller/Sso.php
namespace YourVendor\BtSso\Pub\Controller;
use XF\Mvc\ParameterBag;
use XF\Pub\Controller\AbstractController;
use Firebase\JWT\JWT;
class Sso extends AbstractController
{
public function actionIndex(ParameterBag $params)
{
$visitor = \XF::visitor();
if (!$visitor->user_id) {
return $this->redirect('https://bowlingtracker.com/Home/Index');
}
$privateKey = file_get_contents('/etc/ssl/private/bt_private.pem');
$now = time();
$payload = [
'iss' => 'https://yoursite.com/forums',
'aud' => 'https://bowlingtracker.com',
'nbf' => $now - 900,
'exp' => $now + 300,
'ExternalId' => (string)$visitor->user_id,
'Username' => $visitor->username,
];
$jwt = JWT::encode($payload, $privateKey, 'RS256');
return $this->redirect('https://bowlingtracker.com/Home/Index?Token=' . urlencode($jwt));
}
}
Register the route bt-sso pointing to YourVendor\BtSso:Sso in your add-on's routes.php.
Generating Your RSA Key Pair
If you need to generate a new key pair, run the following OpenSSL commands on your server:
# 1. Generate the private key (keep this secret — never share it)
openssl genpkey -algorithm RSA -out bt_private.pem -pkeyopt rsa_keygen_bits:2048
# 2. Derive the public key (send this to BowlingTracker.com)
openssl rsa -pubout -in bt_private.pem -out bt_public.pem
Email bt_public.pem to us along with your desired Issuer string. We will register your public key and confirm your Issuer before tokens will be accepted.
Testing Your Integration
- Generate a token manually using the OpenSSL or language snippet above.
- Paste the JWT at jwt.io and verify the header shows
"alg": "RS256", and the payload contains your expected claims. -
Append it to the URL and open it in a browser:
https://bowlingtracker.com/Home/Index?Token=<your_jwt> - You should be automatically logged in under your username.
Security Checklist
- ✅ Private key stored outside the web root with restricted file permissions (
chmod 600). - ✅ Token
expset to 5 minutes or less. - ✅ HTTPS enforced on your site so tokens are never transmitted in plaintext.
- ✅ Never log or cache generated tokens.
- ✅ Rotate your key pair immediately if it is ever compromised — contact us to update your registered public key.
Contact & Support
For registration, key exchange, or technical questions, contact us via the BowlingTracker.com forum or email sso@bowlingtracker.com.