Developer documentation

Add one secure NeonLogin account to your application.

NeonLogin uses a browser redirect, a consent screen, and a signed callback. Register your application to receive its own signing secret and allowlist its callback domains.

Request SSO access

  1. Create or sign in to your NeonLogin account.
  2. Open the Developer Portal and register your app name, type, primary domain, and any callback subdomains.
  3. Copy the one-time app secret and store it only on your server.

Your registered domain is used for callback allowlisting and to select the correct app secret.

Continue to SSO access

What your app receives

After the user approves access, NeonLogin redirects to your callback with:

state        Your original anti-forgery value
client_id    Registered NeonLogin app ID
redirect_uri Exact registered callback URI
sig_version  Signed callback format version
neon_id   Stable NeonLogin user ID
username  NeonLogin username
email     User email address
ts        Unix timestamp
sig       HMAC-SHA256 signature
flow      login or link
scope     Requested scope

Only request and retain user data your application actually needs.

Signed callback integration

1. Send the user to NeonLogin

Create a cryptographically random state, save it in the user's server-side session, and redirect the browser:

https://teammidnite.tv/NeonLogin/?return_to=https%3A%2F%2Fexample.com%2Fauth%2Fneonlogin%2Fcallback&client_id=YOUR_APP_UID&state=RANDOM_VALUE&flow=login&scope=basic

The callback URL, client ID, flow, and scope are captured in a server-side authorization transaction. Do not change them after starting the request.

2. Verify the callback on your server

Reject the callback unless state exactly matches the unused value in the user's session, the timestamp is recent, and the signature matches.

<?php
$payload = json_encode([
    'client_id' => $_GET['client_id'],
    'redirect_uri' => $_GET['redirect_uri'],
    'flow' => $_GET['flow'],
    'scope' => $_GET['scope'],
    'neon_id' => $_GET['neon_id'],
    'username' => $_GET['username'],
    'email' => $_GET['email'],
    'ts' => $_GET['ts'],
    'state' => $_GET['state'],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);

$expected = hash_hmac('sha256', $payload, getenv('NEONLOGIN_APP_SECRET'));
if (!hash_equals($expected, (string) $_GET['sig'])) {
    http_response_code(401);
    exit('Invalid NeonLogin signature');
}

if (abs(time() - (int) $_GET['ts']) > 300) {
    http_response_code(401);
    exit('Expired NeonLogin callback');
}

// Also require the exact client_id, redirect_uri, flow, scope, and unused
// state value saved when this authorization attempt began.

3. Create your local session

Look up the account by neon_id, link or provision it according to your application's policy, rotate the local session ID, and mark the saved state value as consumed.

Production security checklist

  • Keep the app secret server-side; never put it in JavaScript, mobile binaries, logs, or source control.
  • Require HTTPS for production callback URLs.
  • Use a new, unpredictable state value for every attempt and consume it once.
  • Register the exact HTTPS callback URI, including port, path, and query when applicable.
  • Keep the client ID, callback URI, flow, and scope bound to the original authorization transaction.
  • Use hash_equals for constant-time signature comparison.
  • Reject stale timestamps and callbacks with missing fields.
  • Rotate a secret immediately if it may have been exposed.

Scopes and flows

flow=login signs a user into your app. Use flow=link when attaching NeonLogin to an account that is already authenticated. Your local application must enforce that linking request.

scope=basic is the standard signed-callback profile. The callback includes the stable user ID, username, and email shown on the consent screen, plus the registered application and callback context.

The signed callback format is a custom NeonLogin protocol, not OAuth 2.0 or OpenID Connect. New integrations use signature version 2 with a registered client_id. Legacy version 1 callbacks require an explicit legacy=1 migration flag and should be retired after migration.

Need API keys or webhooks? Create them under the registered app in the Developer Portal. Available API scopes include users:read_basic, users:read_email, and accounts:read.