Compatibility
Minecraft: Java Edition
Platforms
Supported environments
Tags
Creators
Details
OpenQQAuth
QQ-driven login & account authentication system for Minecraft — an open-source MQE alternative and beyond.
OpenQQAuth runs on the Velocity proxy: players are isolated behind a graphical authentication gate (dialog screens, virtual chest menu, virtual anvil — adapted across every protocol generation) until their identity is verified, then released to the backend Paper server. It uses QQ as the mandatory ownership credential for every account and supports three login methods:
- Offline password login — enter your password in the virtual anvil
- Premium fast login — encrypted handshake + hasJoined verification, passwordless once bound to QQ
- Bedrock login — Microsoft account identity (Floodgate), passwordless after QQ binding, with optional inheritance of a Java account's data
It also adds a login token system: after installing the Fabric / NeoForge dual-platform mod, players can click "Connect with Token" in the multiplayer screen and enter a credential privately sent by the QQ group bot for instant passwordless entry (30-day validity, revocable).
Domain glossary:
CONTEXT.md· Architecture decisions:docs/adr/· Spec:.scratch/qqauth/spec.md· External API research:.scratch/qqauth/api-reference.md
Table of Contents
- Features
- Architecture
- Requirements
- Quick Start
- Configuration Reference
- QQ Group Commands
- In-Game Commands
- Login & Binding Flows
- Login Tokens (mod)
- Chat Sync
- Permission Model
- Building from Source
- Supported Versions
- Development Guide
- FAQ
- License
Features
- Gate: unverified connections are confined to the login screen until authenticated, then released to the backend server
- Graphical authentication UI: dialog screens (1.21.6+) and virtual chest menus (older versions) for login-method selection; password entry always uses a virtual anvil (open / rename / left-click confirm / right-click clear / gray concrete to reset)
- Full protocol-generation support: anvil packets and item serialization are parameterized per protocol family (Legacy 1.7.2~1.12.2 / Flat 1.13~1.14.4 / Classic NBT 1.15~1.20.4 / Components 1.20.5~1.21.1 / Menu Refactor 1.21.2~1.21.5 / Dialog 1.21.6+), with rename-transport split (≤1.12.2 plugin channel / 1.13+ rename packet)
- Three login methods: offline password, premium fast login (encrypted handshake + hasJoined double check), and passwordless Bedrock
- QQ binding: verification-code flow (in-game code → send
bind <code>in the group; sender QQ must match the target QQ, preventing code sniping) - Premium binding: clickable chat hint to trigger, premium session + QQ verification code as two factors;
premiumUuid-based matching survives name changes;/opa bindpremiumfor cross-name binding; impersonation protection (reject offline logins on premium-bound names + anti-sockpuppet account creation) - Bedrock support: Floodgate detection, Cumulus form binding,
/opa bindxboxinheritance binding (share a Java account's location/inventory), Java/Bedrock mutual exclusivity,/opa unbindxboxto restore an independent identity - Fast login: same-IP reconnect within 30 minutes after ≥10 minutes online is passwordless (once per window); QQ notification on trigger ("not you? reply freeze", toggleable)
- Login tokens:
获取凭证in the group → bot sends a credential privately → mod-side "Connect with Token"; 30-day validity, revocable; replay-protected (one-time nonce challenge-response) - Remote self-service & administration from QQ:
kick/freeze/unfreeze/ban/unban/list/status/check/exec/reload/reconnect - Voluntary transfer: on name conflict, the original owner unbinds → admin confirms via
transfer confirm→ target takes over (ADR-0007, no forced takeover path) - Domain events:
PlayerAuthenticatedEvent/AccountBoundEvent/AccountFrozenEvent/AccountBannedEventpublished for other plugins via Bukkit ServicesManager - Read-only API:
ReadOnlyAuthApifor Paper-side plugins (economy / land claims / whitelist integrations) - Chat sync: optional bidirectional group ↔ in-game chat forwarding
- i18n: bilingual messages (zh_CN / en_US) with full translation-key coverage
- Security: password hashing (jBCrypt), failed-attempt lockout (5 tries / 1 min), per-IP session limits, bind rate limiting, session timeouts, replay protection, audit log for all admin operations
Architecture
┌──────────────┐ ┌──────────────────────────────┐ ┌──────────────┐
│ Minecraft │────▶│ Velocity (OpenQQAuth gate) │────▶│ Paper server │
│ client │ │ dialog/chest/anvil auth UI │ │ (API/events) │
└──────────────┘ │ LoginPipeline decision core │ └──────────────┘
│ handshake/premium/Bedrock │
└──────────────┬───────────────┘
│ OneBot (WebSocket)
┌──────────────▼───────────────┐
│ QQ group bot (bind/admin) │
└──────────────────────────────┘
| Module | Description |
|---|---|
core |
All domain logic: accounts / verify codes / state machines / login pipeline / tokens / event bus / command framework, platform-independent |
bootstrap/velocity |
Velocity proxy plugin: gate, virtual window UI, protocol-family codecs, OneBot wiring, mod login queries |
bootstrap/paper |
Paper plugin: read-only API exposure (Bukkit ServicesManager), domain-event subscription example |
bootstrap/bukkit / bungeecord |
Template compatibility skeletons (load-placeholder only) |
bootstrap/fabric + bootstrap/neoforge |
Dual-platform jar: client-side "Connect with Token" UI & token transport, server-side placeholder |
build-logic |
Gradle convention plugins (unified Java/Kotlin compile, checkstyle, spotless) |
Requirements
- Java 21 (build & run)
- Gradle 9.6.1 (system-installed; this project does not use the Gradle wrapper)
- Velocity proxy (the production login-gate host)
- An OneBot 11 implementation (e.g. NapCat / go-cqhttp) over WebSocket
- Paper server (backend)
- Optional: Floodgate / Geyser (Bedrock support), Fabric / NeoForge client mod (token login)
Quick Start
1. Build
gradle --no-daemon :bootstrap:velocity:build # Velocity plugin
gradle --no-daemon :bootstrap:paper:build # Paper plugin
gradle --no-daemon :bootstrap:fabric:build :bootstrap:neoforge:build # mod dual-platform jar
Artifacts land in each module's build/libs/.
2. Install
- Put the velocity plugin jar in
velocity/plugins/, the paper jar inpaper/plugins/ - Start Velocity once to generate the default
config.yml, then edit as needed (see Configuration Reference) - Configure the QQ bot (OneBot 11 WebSocket): set
qq-bot.ws-urlandqq-bot.ws-token, and add admin QQs toqq-bot.admin-qqs - Restart Velocity; send
reloadin the group or run/opa reloadas an admin to hot-reload config
3. Minimal working configuration
config-version: 1
qq-bot:
ws-url: "ws://127.0.0.1:6700" # OneBot WebSocket endpoint
ws-token: "" # access token (if auth enabled)
admin-qqs: # admin QQ list
- "10001"
login:
max-failed-attempts: 5 # password lockout threshold
lockout-minutes: 1 # lockout duration (minutes)
storage:
driver: sqlite
sqlite-file: openqqauth.db
Configuration Reference
All configuration lives in config.yml in the Velocity plugin directory (QqAuthConfiguration, loaded via Configurate).
qq-bot (OneBot)
| Key | Default | Description |
|---|---|---|
qq-bot.ws-url |
ws://127.0.0.1:6700 |
OneBot 11 WebSocket endpoint |
qq-bot.ws-token |
"" |
Access token |
qq-bot.admin-qqs |
[] |
Admin QQ list (ADMIN permission) |
accounts
| Key | Default | Description |
|---|---|---|
accounts.per-qq-limit |
3 |
Max accounts bindable to a single QQ |
login
| Key | Default | Description |
|---|---|---|
login.fast-login-window |
30 |
Fast-login window (minutes): passwordless reconnect time span |
login.fast-login-min-online-minutes |
10 |
Prior online time required to trigger fast login (minutes) |
login.fast-login-notify-qq |
true |
Notify the bound QQ when fast login triggers |
login.max-failed-attempts |
5 |
Password lockout threshold |
login.lockout-minutes |
1 |
Lockout duration (minutes) |
login.max-sessions-per-ip |
3 |
Max concurrent sessions per IP (excess rejected) |
login.bind-frequency-per-minute |
3 |
Bind rate limit (per minute) |
login.session-timeout-seconds |
300 |
Session timeout kick (seconds) |
login.bind-timeout-seconds |
180 |
Binding flow timeout (seconds) |
login.inherit-bind-window-minutes |
5 |
Bedrock inheritance-binding window (minutes) |
login.max-name-length |
30 |
Max rename input length |
verify-code
| Key | Default | Description |
|---|---|---|
verify-code.length |
4 |
Verification code length |
verify-code.expire-seconds |
180 |
Verification code validity (seconds) |
token
| Key | Default | Description |
|---|---|---|
token.enabled |
true |
Enable the token login path |
token.ttl-days |
30 |
Token validity (days) |
sync-groups (chat sync)
| Key | Default | Description |
|---|---|---|
sync-groups.enabled |
false |
Enable message sync |
sync-groups.group-ids |
[] |
Group IDs participating in sync |
sync-groups.game-to-group |
true |
In-game → group forwarding |
sync-groups.group-to-game |
true |
Group → in-game forwarding |
sync-groups.merge-window-seconds |
5 |
Merge window (seconds) |
sync-groups.max-forwards-per-minute |
10 |
Max forwards per minute |
storage
| Key | Default | Description |
|---|---|---|
storage.driver |
sqlite |
Storage driver (SQLite currently) |
storage.sqlite-file |
openqqauth.db |
SQLite database file path |
premium (premium detection)
| Key | Default | Description |
|---|---|---|
premium.enabled |
true |
Enable premium detection |
premium.name-query-cache-hours |
24 |
Name→UUID query cache duration (hours) |
premium.http-timeout-seconds |
5 |
Mojang API timeout (seconds) |
premium.rate-limit-per-window |
600 |
Max queries per rate-limit window |
premium.rate-limit-window-minutes |
10 |
Rate-limit window (minutes) |
premium.bind-wait-minutes |
5 |
Premium binding wait-marker duration (minutes) |
QQ Group Commands
Commands are received by the QQ group bot. Permission tiers: see Permission Model.
Player self-service (BOUND_MEMBER)
| Command | Description |
|---|---|
kick |
Kick all online accounts of the sender (self-service against abnormal logins) |
freeze |
Freeze the sender's accounts (temporary, self-reversible) |
unfreeze |
Unfreeze the sender's accounts |
status |
Show the sender's account info & verification state |
list |
Show the online list |
获取凭证 |
Issue a new login token for the sender's account and send it privately (re-issuing overrides the old one) |
吊销凭证 [<account>] |
Immediately revoke the tokens of the sender's accounts |
Verification-code commands (in-group confirmation)
| Command | Description |
|---|---|
bind <code> |
Confirm QQ binding (code shown on the in-game screen) |
重置 <code> |
Confirm password reset (started from the gray concrete in the anvil) |
解绑正版 <code> |
Unbind the premium identity |
解绑基岩 <code> |
Unbind the Bedrock account (restore independent identity) |
Admin commands (ADMIN)
| Command | Description |
|---|---|
bind 1 <QQ> <player> |
Force-bind an account (a token is then sent privately) |
bind 2 <QQ> |
Reset an account's password (new password printed to console) |
ban @<target> [name] / unban |
Ban / unban an account (persistent, admin-only) |
freeze @<target> [name] / unfreeze |
Freeze / unfreeze any account (temporary, reversible) |
check |
View any player's full info & server overview |
exec <server> <command> |
Run a command on the backend server (audit-logged before/after) |
transfer confirm <account> <targetQQ> <premiumName> |
Confirm a voluntary transfer (after the owner unbinds) |
transfer list |
List pending transfer confirmations |
reload |
Hot-reload configuration |
reconnect |
Reconnect the QQ bot |
In-Game Commands
In-game commands use the /opa prefix (Velocity command registration):
| Command | Description |
|---|---|
/opa bindpremium <name> |
Cross-name premium binding (premium identity takes over the original offline account) |
/opa bindxbox <name> |
Start Bedrock inheritance binding (Bedrock account → specified Java account, sharing location/inventory) |
/opa unbindxbox |
Unbind Bedrock inheritance (restore independent identity, data kept) |
/opa reload |
Hot-reload configuration (admin) |
Login & Binding Flows
New player (offline)
- Join the server → intercepted by the gate, enters the QQ binding flow directly
- Enter your QQ number → the in-game screen shows a verification code
- Send
bind <code>in the QQ group (sender QQ must match the entered QQ — anti-sniping) - Binding succeeds → create a password in the anvil (two confirmation inputs) → released into the server
Returning player (offline)
- Select "offline" on the login screen → enter your password in the anvil → verified and released
- Forgot your password: click the gray concrete in the anvil to start a reset → set a new password → confirm with
重置 <code>in the group
Premium player
- When the name matches a premium account, the chat shows "detected [id] has a premium account — bind?"
- Click the hint → kicked with "use a premium client to join"
- Reconnect with the premium client → encrypted handshake + hasJoined verification → send
绑定正版 <code>in the group to complete the QQ second factor - After binding, premium logins are passwordless (account matched by
premiumUuidwith identity rewrite; name changes don't matter) - Same person with a different name:
/opa bindpremiumfor manual cross-name binding
Bedrock player (Floodgate)
- Floodgate detects the Bedrock connection → skips the Java login screens, uses a Cumulus form to enter the QQ number directly
- Send
bind <code>in the group to confirm → bound and released (passwordless) /opa bindxbox: inheritance-bind the Bedrock account to a Java account (shared location/inventory), Java/Bedrock mutually exclusive online/opa unbindxboxor解绑基岩in the group: restore an independent identity (data kept while under the QQ account limit)
Fast login
- On reconnect: same IP within 30 minutes, previously online ≥10 minutes → passwordless auto-release (once per window)
- QQ notification on trigger: "account <name> just joined via fast login — not you? reply freeze" (toggleable via
fast-login-notify-qq)
Login Tokens (mod)
For players
- Send
获取凭证in the QQ group → the bot privately sends a 30-day token - Launch a client with the OpenQQAuth mod installed (Fabric or NeoForge)
- Click "Connect with Token" in the bottom-right of the multiplayer screen → enter the token → connect
- The server verifies and releases you passwordless; revoke at any time with
吊销凭证if the token leaks
How it works
- During the login phase the token is sent over the mod channel (
openqqauth:token); the Velocity side runs a 128-bit random nonce challenge-response to prevent replay - Verification (
TokenLoginChecker): token + account binding + not expired + not revoked → passwordless release on hit - A failed token check never blocks the password path (the player stays at the password screen)
- Non-mod (vanilla) clients are unaffected and use password login as usual
- Optional local plaintext token storage (
save-tokentoggle; cleared when disabled)
Chat Sync
With sync-groups.enabled:
- Group messages → in-game chat (
group-to-game) - In-game chat → group (
game-to-group) - In-game
/opa atto @ a group member,/opa reto reply to a group message
Permission Model
| Tier | Meaning | How to get |
|---|---|---|
ANY |
Any group member | Default |
BOUND_MEMBER |
Player with a bound account | Complete QQ binding |
ADMIN |
Administrator | Set qq-bot.admin-qqs |
Admin commands (bind / ban / exec / check / transfer / reload / reconnect, etc.) are written to the CommandAuditLog before and after execution.
Building from Source
# Full build: compile + test + checkstyle + spotless
gradle --no-daemon build
# Compile a single module
gradle --no-daemon :core:compileJava
gradle --no-daemon :bootstrap:velocity:compileJava
gradle --no-daemon :bootstrap:paper:compileJava
gradle --no-daemon :bootstrap:fabric:compileJava
gradle --no-daemon :bootstrap:neoforge:compileJava
# Run a module's tests
gradle --no-daemon :core:test
gradle --no-daemon :bootstrap:velocity:test
# Style checks
gradle --no-daemon :core:checkstyleMain :core:spotlessCheck
System-installed Gradle 9.6.1 is required (no wrapper). Build memory of
-Xmx2Gis recommended (seegradle.properties).
Supported Versions
Client Minecraft versions (protocol families)
| Protocol family | MC versions | Protocol | Support |
|---|---|---|---|
| Legacy | 1.7.2 ~ 1.12.2 | 4 ~ 340 | ✅ 1.12.2 committed; 1.7.2~1.12.1 theoretical floor, not committed (packet-ID drift) |
| Flat | 1.13 ~ 1.14.4 | 393 ~ 498 | ⚠️ calibrated on 1.14.4; earlier 1.13 sub-versions not committed |
| Classic NBT | 1.15 ~ 1.20.4 | 573 ~ 765 | ✅ |
| Components | 1.20.5 ~ 1.21.1 | 766 ~ 767 | ✅ |
| Menu Refactor | 1.21.2 ~ 1.21.5 | 768 ~ 770 | ✅ |
| Dialog | 1.21.6 ~ latest (26.2) | 771+ | ✅ |
Conclusion: clients from 1.12.2 up to the latest release (26.2) can log in. The UI auto-dispatches per version: dialog screens for 1.21.6+, virtual chest menus for older versions, and a virtual anvil for password input everywhere (rename split: ≤1.12.2 plugin channel / 1.13+ rename packet; item serialization: NBT ≤1.20.4 / Data Components ≥1.20.5).
Runtime requirements
| Side | Version |
|---|---|
| Velocity proxy | Velocity API 3.5.0-SNAPSHOT (Velocity 3.3.x series), Java 21 |
| Java | 21 |
| Paper server | any version proxied by that Velocity release |
| Gradle | 9.6.1 (build only) |
| mod side (token login) | Fabric (fabric-loom 1.17.9) / NeoForge (ModDevGradle 2.0.141), target MC 26.1.2 (build baseline) |
Known limits
- 1.7.2~1.12.1: the Legacy constant table exists, but this range is marked "theoretical floor, not committed" — packet-ID drift may occur; 1.12.2 is the recommended minimum for production
- mod token side: the build baseline is MC 26.1.2; supporting other MC versions requires bumping
minecraft = "26.1.2"in the build config and rebuilding - real Velocity connection wiring: ServerboundPacketHandler bridging for old families (<770) is a follow-up ticket; verified at the mock layer
Development Guide
- Issues & spec:
.scratch/<feature>/(spec + numbered tickets), seedocs/agents/issue-tracker.md - Triage labels:
needs-triage/needs-info/ready-for-agent/ready-for-human/wontfix, seedocs/agents/triage-labels.md - Domain glossary:
CONTEXT.md(account / QQ / bind / gate / login screen / premium binding / inheritance binding, etc.) - Architecture decisions:
docs/adr/0001~0008(virtual-anvil gate, offline premium detection, OneBot 11, login-screen layering, login tokens, fixed account keys, name conflict, fast-login trust model) - Collaboration conventions: see
CLAUDE.md(worktree isolation, impact-scope verification, per-ticket commits, concurrency cap)
Core source layout
core/src/main/java/dev/nmouzh/openqqauth/
├── account/ # account model, SQLite repository
├── anvil/ # anvil window state machine (UI-agnostic core)
├── api/ # read-only query API (for Paper plugins)
├── bedrock/ # Bedrock support (form sender, bind flow)
├── configuration/# config model (Configurate)
├── event/ # domain event bus (SimpleDomainEventBus)
├── gate/ # LoginPipeline, sessions, lockout, fast login
├── i18n/ # TranslationKey enum + bilingual resources
├── mojang/ # Mojang API client (name→UUID, hasJoined)
├── onebot/ # OneBot 11 client (WebSocket messaging)
├── password/ # password hashing (jBCrypt)
├── qq/ # QQ domain: bind/reset/unbind state machines, group commands, audit log, voluntary transfer
├── token/ # login tokens: issue/revoke/verify, transport protocol, mod channel
├── util/ # utilities
└── verify/ # verification code service
FAQ
Q: "Bot offline" — can't bind?
A: New-player binding checks the bot's online state (BotOnlineProvider); when the bot is offline, binding operations degrade to a hint + kick. Confirm the OneBot connection (reconnect reconnects).
Q: Old clients like 1.12.2 can't log in? A: The anvil protocol and item serialization are parameterized per protocol family supporting the Legacy range (theoretical floor 1.7.2, 1.12.2 committed). Earlier 1.13 sub-versions and 1.7.2~1.12.1 packet-ID drift are "not committed"; real Velocity connection wiring (internal MinecraftConnection bridging) is a separate follow-up ticket.
Q: What happens if token verification fails? A: It never blocks the password path — the failure is only logged and the player stays at the password screen and can log in normally.
Q: How can other plugins read account info?
A: On Paper, get ReadOnlyAuthApi (account/session queries) via Bukkit ServicesManager, and subscribe to domain events (PlayerAuthenticatedEvent, etc.). See PaperAuthEvents in bootstrap/paper for an integration example.
Q: Where is data stored?
A: By default in the SQLite file openqqauth.db (storage.sqlite-file is configurable).
Q: How are name conflicts handled?
A: Per ADR-0007 ("first come, first served for the past; premium-priority for the future"): conflicting parties can use voluntary transfer (owner unbinds → admin transfer confirm → target takes over). The system provides no forced-takeover path.
License
This project is licensed under CC0 1.0 Universal — free to use however you like, including relicensing for your own projects.


