Client sign-in for games and native apps
Sign players in from Unity, Godot, desktop, and mobile apps through your own backend, with no Enlitt key in the client.
Use this page when your users run something that is not a web page: a Unity or Godot game, a desktop app, or a mobile app. Web pages use the normal cookie login.
The model calls still run on your backend, with a server SDK (Node.js, Python, or .NET). The client never holds an Enlitt key. It only signs the player in and then sends a session header to your backend.
How it works
- The game opens the system browser at your backend's
/startroute. - The player signs in to Enlitt. Enlitt sends them back to your normal https callback, which you already registered.
- Your backend hands a one-time code back to the game, through one of three
modes:
- loopback (desktop and the editor): a redirect to a port on
127.0.0.1that the game listens on; - deep link (mobile): a redirect to your app's link;
- pairing (consoles, TVs, or any device without a browser): the player types a short code on their phone instead.
- loopback (desktop and the editor): a redirect to a port on
- The game redeems the code at your backend's
/tokenroute and receives the session token. - The game sends
Enlitt-Session: <token>on every request to your backend. Your backend reads it with the session middleware and makes model calls as usual.
PKCE protects every mode, so a code that is intercepted is useless without the game's verifier.
Backend: mount the routes
Mount the client sign-in handler next to your login handlers, and turn on the header source of the session middleware.
Node.js:
import { createClientSignInHandler, createMemoryClientSignInStore } from "@enlitt/sdk/server";
import { enlittSessionMiddleware } from "@enlitt/sdk/core";
const clientSignIn = createClientSignInHandler({
callback: config, // the same CallbackConfig your login handlers use
basePath: "/enlitt/client",
appName: "My Game",
returnUrls: ["com.example.mygame:/signed-in"], // only if you use deep links
pairing: true, // only if you support devices without a browser
store: createMemoryClientSignInStore() // development only; use a shared store in production
});
// Route every path under /enlitt/client to clientSignIn.
const withSession = enlittSessionMiddleware({ header: true });Python (FastAPI):
from enlitt.asgi import EnlittSessionMiddleware
from enlitt.client_sign_in import ClientSignInConfig, MemoryClientSignInStore
from enlitt.starlette import enlitt_routes
client_sign_in = ClientSignInConfig(
callback=config,
base_path="/enlitt/client",
app_name="My Game",
pairing=True,
store=MemoryClientSignInStore(),
)
app = FastAPI(routes=enlitt_routes(config=config, client_sign_in=client_sign_in))
app.add_middleware(EnlittSessionMiddleware, header=True)ASP.NET Core:
app.UseEnlittSession(new EnlittSessionMiddlewareOptions { Header = true });
app.MapEnlittAuth(config);
app.MapEnlittClientSignIn(new ClientSignInConfig
{
Callback = config,
BasePath = "/enlitt/client",
AppName = "My Game",
Pairing = true,
Store = new MemoryClientSignInStore(),
});Notes for the backend:
- Store. The in-memory store works only in a single process. With more than
one server, give it a shared store (Redis, your database) that implements
set,get, andconsume. - Rate limits. Rate-limit
POST /enlitt/client/pairandPOST /enlitt/client/tokenper IP address with the limiter you already use. - Website sign-out. Signing in a game signs that browser out of your website, because the session moves to the game.
- Deep links.
returnUrlsmust list each deep link exactly. Use an https app link or a reverse-DNS custom scheme such ascom.example.mygame:/signed-in. - Enlitt registration. No new redirect URL is registered with Enlitt. Your existing https callback is the only one.
Unity
Install the kit from the Package Manager with Add package from git URL:
https://github.com/enlitt/sdk.git?path=packages/unity-signin/upmPin a version by appending its tag, for example #unity-signin-v0.1.0. It needs Unity 2021.3 or later.
using Enlitt.SignIn;
using UnityEngine.Networking;
var signIn = new EnlittSignIn(new EnlittSignInOptions
{
BackendBaseUrl = "https://api.mygame.example/enlitt/client",
ReturnUrl = "com.example.mygame:/signed-in", // mobile only; omit on desktop
});
if (!signIn.IsSignedIn)
await signIn.SignInAsync(); // loopback on desktop and in the editor, deep link on mobile
var request = UnityWebRequest.Get("https://api.mygame.example/api/npc-reply");
signIn.Authorize(request); // adds the Enlitt-Session header
await request.SendWebRequest();For a device without a browser:
var pairing = await signIn.StartPairingAsync();
ShowOnScreen($"Go to {pairing.VerificationUri} and enter {pairing.UserCode}");
await pairing.WaitAsync();Call SignOutAsync() to sign out. If your backend reports that the session is
no longer valid (Enlitt answered personal_login_required), call
ForgetSession() and sign in again.
- Token storage. The kit stores the token in
PlayerPrefs, which is not encrypted. For a shipped mobile game, passTokenStoragean implementation backed by the platform keychain. - WebGL. A WebGL build is a web page, so it uses the normal cookie login instead.
Other engines and native apps
Any client can speak the same routes. All of them are under your basePath.
| Route | What it does |
|---|---|
GET /start?mode=loopback&port=&state=&code_challenge=&code_challenge_method=S256 | Open this in the system browser. It ends at http://127.0.0.1:<port>/callback?code=&state=, or error=access_denied. |
GET /start?mode=redirect&redirect_uri=&state=&code_challenge=&code_challenge_method=S256 | The same, ending at your exact deep link. |
POST /pairing with {"code_challenge", "code_challenge_method": "S256"} | Returns {pairing_id, user_code, verification_uri, expires_in, interval}. Show the code and the URL to the player. |
POST /token with {"grant_type": "code", "code", "code_verifier"} | Returns {session_token}. A code works once, within 5 minutes. |
POST /token with {"grant_type": "pairing", "pairing_id", "code_verifier"} | Poll every interval seconds. It answers authorization_pending until the player confirms, then {session_token}. |
POST /logout with the Enlitt-Session header | Ends the session. It always answers 204. |
- PKCE.
code_verifieris 43 to 128 random URL-safe characters.code_challengeis the unpadded base64url SHA-256 of it. - State.
stateis a random value you choose and check when the code comes back. - Errors. Every
/tokenerror is400with{"error": ...}. The values areinvalid_request,authorization_pending,access_denied,expired_token, andinvalid_grant.
Send the token only to your own backend, as Enlitt-Session.