Sign players in with Velora from the Hyperfocus launcher
If a player is logged in to Hyperfocus, your game can sign them in with Velora without a login screen: the launcher gives the game its own Velora token in the environment variable HF_VELORA_TOKEN (and the username in HF_VELORA_USER) when it starts the game. This works for official games and for community games. You choose the permissions; the player approves them; your game gets a token for exactly those.
How it works
- You turn on
veloraTokenfor the game with a project name and a permissions number. - The player presses Play. Community games show a permission request the first time; official games only show one if they want more than the launcher's own login has.
- Hyperfocus asks Velora for a token for your project (or opens Velora so the player can approve permissions the launcher does not have) and starts the game with
HF_VELORA_TOKENandHF_VELORA_USERset. - Your game reads the variable, calls Velora to check it, and the player is signed in.
Versions: official games get the environment variables with Hyperfocus 1.3.23 or newer. Community games need Hyperfocus 1.3.33-beta.3 or newer (currently a beta, so players on the release channel do not have it yet and the game simply starts without a token).
1. Turn it on
Community games
In the community editor, open your game's details tab and find Velora login for your game. Tick the checkbox, type a project name and tick the permissions your game needs (one checkbox per permission, with what it allows). Sign-in itself (See your profile) is always required. Save.
- The project name is what the player sees on Velora (up to 60 characters; it defaults to the game's name). The names
Velora Session,Hyperfocus LauncherandHyperfocus Communityare reserved and cannot be used. - Permissions the launcher's own login does not have are marked as needing approval on Velora.
- The setting is stored as
{ "project": "...", "scopes": <number> }. Passingnullturns it off.
Official games (the game list)
Official games are listed in apps.json on the Hyperfocus server and managed by the owner, who adds the same field to the game's entry:
{
"id": "mygame",
"name": "My Game",
"veloraToken": { "project": "My Game", "scopes": 17 }
}
scopes is the sum of the permission numbers in the table below. 17 is 1 (profile) plus 16 (read saved data). Leave it out and no token is made.
2. Choose your permissions
Ask for the least you need: players see every line before they press Allow. You can work out the number with the permissions calculator or just add the values below.
| Number | Permission | What the player is told | In the launcher's login? |
|---|---|---|---|
| 1 | PROFILE_READ | See your profile: username, picture and public profile | Yes (always required) |
| 2 | PROFILE_SENSITIVE * | See private profile details, like your email address | No |
| 4 | PROJECT_AUTH | Sign you in to other apps | No |
| 8 | ACCOUNT_WRITE * | Change your account settings | No |
| 16 | STORAGE_READ | Read its saved data (what it stored in your Velora storage) | Yes |
| 32 | STORAGE_WRITE * | Save data to your account (uses your Velora storage) | Yes |
| 64 | VMAIL_READ | Read your messages | Yes |
| 128 | VMAIL_SEND | Send messages as you | Yes |
| 256 | FRIENDS_READ | See your friends | Yes |
| 512 | FRIENDS_MANAGE * | Add and remove friends | Yes |
| 1024 | CURRENCY_PAY * | Spend your Velora credits (payments still ask you first) | No |
| 2048 | CHAT | Chat as you | Yes |
| 4096 | MEDIA_UPLOAD | Upload pictures and media | Yes |
| 8192 | CURRENCY_READ | See your Velora credits (your balance) | No |
| 16384 | VOICE | Join voice chats and call people | Yes |
| 32768 | SIGN_MESSAGES * | Signing Ember chat messages with a device key (the launcher's prompt has no line for it; it is not useful for games) | No |
* Marked as dangerous on Velora: unchecked by default on Velora's own consent screen. The numbers come from the Velora server; the "In the launcher's login?" column is what the launcher currently asks for when the player signs in to Hyperfocus (the sum is 23537). Permissions that column says "No" for can only reach your game after the player approves them on Velora's page.
Keep the project name and permission number stable. When the number changes, community games ask the player again.
3. The permission prompt
Community games: the first launch always asks
The first time a player launches a community game that uses Velora login, Hyperfocus shows a permission request window: "<game> wants to sign in with your Velora account", the game's icon and author, and every permission the game asked for. The player picks:
- Allow - the game gets its own Velora login with those permissions. If any of them are not in the launcher's login, Velora opens in the player's browser so they can approve them there (the prompt waits up to 5 minutes), and then the game starts. Otherwise it starts straight away.
- Play without it - the game starts with no token and signs the player in itself.
- Cancel - the game does not start.
The answer is remembered for this game, project name, permission number and Velora account. If you change the permissions, or the player logs in as someone else, it asks again. Players can change their mind under the game's Game settings in Hyperfocus (Revoke or Ask again).
Official games: only for what the launcher login lacks
Official games start without a prompt as long as the launcher login already has every permission they ask for. If the game wants more, the player sees the same window (with the new permissions tagged) and chooses Allow, Play without them (the game gets a token with only the permissions the launcher has) or Cancel. An approved game login is saved encrypted on the player's PC and reused while it is still valid and belongs to the same account.
4. What your game receives
| Variable | Value |
|---|---|
HF_VELORA_TOKEN | A Velora token for your project. Treat it as a password. |
HF_VELORA_USER | The player's Velora username. Convenience only. |
Both are missing when:
- the game was not launched from Hyperfocus, or was not started as an
.exe,.cmdor.batfile; - the player is not logged in to Hyperfocus, is in Offline Mode, or Velora could not be reached;
- the player chose Play without it (community games);
- the game has no
veloraTokensetting.
Always keep a way to sign in that does not need the variable. It is also a good idea to read both variables once at startup and remove them from the environment so child processes do not inherit the token.
5. Validate the token
The token is a normal Velora bearer token. Check it with Velora before you trust anything about the player:
POST https://velora.ikelene.net/auth/validate
Authorization: Bearer <HF_VELORA_TOKEN>
Content-Type: application/json
{}
A good token answers 200 with { "valid": true, "user": { ... } }; a bad or expired one answers 401 with { "valid": false }. From user use:
username,id,rank,pfp_urlandcolor- who the player is and how their profile looks. Theidis the account's ID, which is what to store your own data against.scopes- the permission number the token really has. Check that the bits you need are set ((scopes & 17) === 17).project_name- the project the token was made for. Check that it is your project name so a token from another app is not accepted.
Read only id, username, rank, pfp_url, color, scopes and project_name. A token without the PROFILE_SENSITIVE permission only gets the public profile: no email address, no Discord ID, no credits, no login statistics and no suspension information. To receive the email you must ask for PROFILE_SENSITIVE (number 2), which always asks the player on Velora's own page.
/auth/validate. Never send the token anywhere except your own server and velora.ikelene.net.6. Use the token
Send it as Authorization: Bearer <token> on Velora API calls, within the permissions it has: for example storage with the storage permissions, or friends with FRIENDS_READ. See the developer docs and the Velora API guide for the routes.
- A token that is not used for 7 days stops working. Each call counts as use.
- Velora keeps only the newest 3 launcher-made tokens per project and player and revokes older ones, so read
HF_VELORA_TOKENon every launch and do not rely on an old copy. - The player can revoke the token from their Velora account at any time. Handle
401by falling back to the sign-in below.
7. Falling back to your own sign-in
When there is no token, sign the player in the normal Velora way: ask Velora for a code, open the sign-in page in the browser, and poll until the player approves. This is the same flow Hyperfocus itself uses.
// 1. POST /auth/ext-code { permissions_integer, project_name } -> { code }
// 2. open https://velora.ikelene.net/login?code=...&project=...&permissions=...
// 3. poll GET /auth/ext-code/status?code=... until { ready: true, token } (the code lives about 5 minutes)
const API = 'https://velora.ikelene.net';
async function signInYourself(project, scopes, openInBrowser) {
const res = await fetch(`${API}/auth/ext-code`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ permissions_integer: scopes, project_name: project }),
});
const { code } = await res.json();
openInBrowser(`${API}/login?code=${encodeURIComponent(code)}&project=${encodeURIComponent(project)}&permissions=${scopes}`);
for (let i = 0; i < 200; i++) { // about 5 minutes
await new Promise(r => setTimeout(r, 1500));
const s = await fetch(`${API}/auth/ext-code/status?code=${encodeURIComponent(code)}`);
if (s.status === 404) return null; // the code expired
const j = await s.json();
if (j.ready && j.token) return j.token;
}
return null;
}
Save the token you get this way somewhere private (Electron's safeStorage, the Windows credential store, an encrypted file) and try it first on the next launch, after the launcher's token.
Examples
All examples do the same: read the variable, ask Velora who it belongs to, and fall back when anything is missing.
Node.js / Electron (main process)
const API = 'https://velora.ikelene.net';
const PROJECT = 'My Game';
const NEED = 1; // PROFILE_READ
// read once, then take it out of the environment so child processes do not inherit it
const launcherToken = process.env.HF_VELORA_TOKEN || null;
delete process.env.HF_VELORA_TOKEN;
async function validate(token) {
try {
const res = await fetch(`${API}/auth/validate`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: '{}',
});
if (!res.ok) return null;
const data = await res.json();
const u = data.valid ? data.user : null;
if (!u || u.project_name !== PROJECT || (u.scopes & NEED) !== NEED) return null;
return u;
} catch {
return null;
}
}
async function getLogin() {
if (launcherToken) {
const user = await validate(launcherToken);
if (user) return { token: launcherToken, user };
}
const token = await signInYourself(PROJECT, NEED, url => require('electron').shell.openExternal(url));
const user = token && await validate(token);
return user ? { token, user } : null;
}
Python
import json
import os
import urllib.error
import urllib.request
API = "https://velora.ikelene.net"
PROJECT = "My Game"
NEED = 1 # PROFILE_READ
token = os.environ.get("HF_VELORA_TOKEN")
os.environ.pop("HF_VELORA_TOKEN", None) # do not pass it on to child processes
def validate(token):
req = urllib.request.Request(
API + "/auth/validate",
data=b"{}",
method="POST",
headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=10) as res:
data = json.load(res)
except (urllib.error.URLError, ValueError):
return None
user = data.get("user") if data.get("valid") else None
if not user or user.get("project_name") != PROJECT or (user.get("scopes", 0) & NEED) != NEED:
return None
return user
user = validate(token) if token else None
if user:
print("Signed in as", user["username"])
else:
print("No launcher login - use your own sign-in")
Godot 4 (GDScript)
extends Node
const API := "https://velora.ikelene.net"
const PROJECT := "My Game"
const NEED := 1 # PROFILE_READ
var username := ""
var token := ""
func _ready() -> void:
token = OS.get_environment("HF_VELORA_TOKEN")
if token == "":
_use_own_sign_in()
return
var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(_on_validated)
var headers := PackedStringArray(["Authorization: Bearer " + token, "Content-Type: application/json"])
if http.request(API + "/auth/validate", headers, HTTPClient.METHOD_POST, "{}") != OK:
_use_own_sign_in()
func _on_validated(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS or code != 200:
_use_own_sign_in()
return
var data = JSON.parse_string(body.get_string_from_utf8())
var user = data.get("user") if typeof(data) == TYPE_DICTIONARY and data.get("valid", false) else null
if user == null or user.get("project_name", "") != PROJECT or (int(user.get("scopes", 0)) & NEED) != NEED:
_use_own_sign_in()
return
username = str(user["username"])
print("Signed in as ", username)
func _use_own_sign_in() -> void:
token = ""
# show your own Velora sign-in here (see "Falling back to your own sign-in")
pass
This runs in a Godot export launched by Hyperfocus. In the editor OS.get_environment returns an empty string, which takes the fallback path.
Unity (C#)
using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
public class VeloraLogin : MonoBehaviour
{
const string Api = "https://velora.ikelene.net";
const string Project = "My Game";
const int Need = 1; // PROFILE_READ
[Serializable] class User { public string username; public int scopes; public string project_name; }
[Serializable] class Reply { public bool valid; public User user; }
public string Username { get; private set; }
IEnumerator Start()
{
string token = Environment.GetEnvironmentVariable("HF_VELORA_TOKEN");
if (string.IsNullOrEmpty(token)) { UseOwnSignIn(); yield break; }
using (var req = new UnityWebRequest(Api + "/auth/validate", "POST"))
{
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes("{}"));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
req.SetRequestHeader("Authorization", "Bearer " + token);
yield return req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success) { UseOwnSignIn(); yield break; }
var reply = JsonUtility.FromJson<Reply>(req.downloadHandler.text);
if (reply == null || !reply.valid || reply.user == null
|| reply.user.project_name != Project || (reply.user.scopes & Need) != Need)
{ UseOwnSignIn(); yield break; }
Username = reply.user.username;
Debug.Log("Signed in as " + Username);
}
}
void UseOwnSignIn()
{
// show your own Velora sign-in here (see "Falling back to your own sign-in")
}
}
Environment variables are not available in the Unity editor unless you set them there, and a WebGL build cannot read them at all.
TurboWarp and PenguinMod games
A project packaged with the TurboWarp or PenguinMod packager runs inside a web page, which cannot read the launcher's environment variables. The Hyperfocus Scratch extension does not hand out a token either: its player blocks (logged in to Velora?, Velora username, picture, colour, rank) are for display and are not proof of who the player is.
For a Scratch project that needs a real login, use the Velora extension inside the project itself: it opens Velora's sign-in window and gives the project a token with the permissions you choose. The steps are in How to Add Velora Login to Your PenguinMod Project and the TurboWarp login extension guide. In a packaged app the Velora extension needs an internet connection like any other Velora call.
Troubleshooting
| Problem | Likely cause |
|---|---|
| The variable is empty | The game was not started by Hyperfocus, the player is logged out or offline, the player chose Play without it, or the game was opened with something other than an .exe, .cmd or .bat file. |
| No permission prompt appears | Community games always ask on the first launch; if it never shows, check that veloraToken is saved on the details tab and the game was reloaded in Hyperfocus (F5 or refocus the window). Official games only ask for permissions beyond the launcher's login. |
/auth/validate returns 401 | The token was revoked, expired after 7 days without use, or is not a Velora token. |
| The scopes are fewer than you asked for | The player chose Play without them (official games), or an administrator switched a service off for the account. Check scopes and degrade gracefully. |
| It asks the player every launch | The project name or permission number changes between launches, or the player keeps using different Velora accounts. |
Questions
Is the token the launcher's own login?
No. The game gets its own token, limited to the permissions you asked for and to what the player allowed. Hyperfocus never hands a game its own token or the player's password.
What if the player says no?
For community games the player can choose Play without it, and the game starts with no token. Your game must then offer its own sign-in. The same applies when the player is not logged in to Hyperfocus, is offline, or Velora is unreachable.
Can I trust HF_VELORA_USER?
Only as a display name. If anything depends on who the player is, validate HF_VELORA_TOKEN with POST /auth/validate, and do it on your own server if you have one.
Does this work for TurboWarp and PenguinMod games?
Not through the environment variable: Scratch projects cannot read it, and the Hyperfocus extension does not return a token. Use the Velora extension's own sign-in blocks inside the project.
How long does the token last?
A Velora token stops working after 7 days without being used, and the player can revoke it. Hyperfocus makes a fresh one each time the game starts, so read the variable on every launch.