AuthCore, Development & Architecture
This document explains how AuthCore is built, how the codebase is organized and how multi-version / multi-loader support is managed so contributors can extend the mod without breaking the 1.16.x β 26.1-26.2 compatibility promise.
1. Overview
AuthCore is a universal authentication framework for Minecraft servers:
- One jar is simultaneously a server mod, a client companion mod and a BungeeCord/Velocity proxy plugin (the loader picks the right entrypoint at runtime).
- One codebase targets Minecraft 1.16.0 β 26.1-26.2 across all three loaders - Fabric, Forge and NeoForge (7 released jars: 3 version ranges Γ loaders).
- Compatibility is not assumed, it is verified by the host-test harness on every version-group endpoint inside Docker (see Β§6).
2. Build system
| Layer | Choice | Why |
|---|---|---|
| Gradle version-gating | Stonecutter 0.9 (dev.kikugie.stonecutter) |
One source tree, per-version code generated from /*? if ... {*/ preprocessor comments |
| Multi-loader wiring | Stonecraft 1.10 (gg.meza.stonecraft) |
Wires Stonecutter + Architectury Loom + Mod-Publish-Plugin; registers the fabric / forge / neoforge / forgeLike / fabricLike constants and per-version dependencies |
| Mappings | Mojang (mojmap) for every version | One naming convention 1.16.5 β 26.1-26.2; 26.1-26.2 is unobfuscated so dev names == runtime names |
| Toolchains | foojay resolver, JDK 17 / 21 / 25 | Java level follows the group: G1=17, G2=21, G3=25 |
Key files
settings.gradle.kts # plugin management + the version/loader matrix (stonecutter.shared)
stonecutter.gradle.kts # auto-managed "active" variant (IDE + bare `gradlew` use this one)
build.gradle.kts # central build script, evaluated once per variant
gradle.properties # mod metadata + third-party library versions
versions/dependencies/
1.18.2.properties # per-version loader/API pins (minecraft, fabric, forge, neoforge)
1.21.11.properties
26.2.properties
The variant model
Every released jar is one variant (a generated Gradle project in versions/<mc>-<loader>/).
stonecutter.shared in settings.gradle.kts declares the matrix:
mc("1.18.2", "fabric", "forge") // G1 jars: authcore-1.16-1.18-{fabric,forge}-<v>.jar
mc("1.21.11", "fabric", "forge", "neoforge") // G2 jars: authcore-1.19-1.21-{fabric,forge,neoforge}-<v>.jar
mc("26.2", "fabric", "neoforge") // G3 jars: authcore-26.1-26.2-{fabric,neoforge}-<v>.jar
vcsVersion = "1.21.11-fabric" is the active variant: the raw template sources are
written in its output state (see Β§4), IDEs and gradlew build act on it.
Building
./gradlew :1.21.11-fabric:build # build one variant
./gradlew build # build the active variant only
./gradlew chiseledBuild # build ALL variants (fan-out)
./gradlew :<variant>:collectJars # copy a variant jar into build/libs
Notes:
- Gradle itself must run on JDK 25 when a 26.1-26.2 variant is configured (MC 26.2 enforces it); the foojay resolver downloads 17/21/25 toolchains automatically.
- Runtime libraries (SQLite/MySQL/PostgreSQL JDBC, Redis, HOCON configurate, password4j,
bouncycastle, gson, kotlin-stdlib, two-factor-auth) are shaded into the jar with
Loom's
include; Floodgate, LuckPerms, Bungee/ Velocity APIs arecompileOnly. - Released artifacts land in
dist/asauthcore-<range>-<loader>-<modversion>.jar.
3. Source architecture
src/
βββ fabric/ # fabric entrypoint only: FabricEntry.java
βββ neoforge/ # neoforge entrypoint only: NeoForgeEntry.java
βββ main/java/net/ded3ec/
βββ AuthCoreServer.java # main entrypoint (loader-neutral start()) + startup banner
βββ api/ # AuthCoreApi - public API for other mods
βββ client/ # client companion: custom login screen (LoginScreen) + ClientAuthCore
βββ command/ # /authcore command trees (Admin, Account, Login, Register, Discord)
βββ compat/ # Compat - reflection bridges for version-drifting APIs
βββ entrypoint/ # ForgeEntry (+ ForgeEntryModern) - forge @Mod classes
βββ events/ # block/item/entity interaction + player join handlers
βββ mixin/ # server mixins (+ mixin/client for the companion)
βββ models/ # Config, Lobby, Messages, User (session state)
βββ network/ # web panel, interop channel, Mojang/GeoIP lookups, Redis
βββ proxy/ # BungeeCord / Velocity plugin entrypoints (same jar!)
βββ security/ # Security, Honeypot, RateLimiter, SecurityLog, Backups, ...
βββ util/ # Database, HoconConf, Registry, FabricHooks, Logger, ...
src/main/resources/
βββ fabric.mod.json # fabric metadata (placeholders expanded at build time)
βββ META-INF/mods.toml # forge metadata (excluded from fabric jars)
βββ META-INF/neoforge.mods.toml # neoforge metadata (excluded from fabric jars)
βββ authcore.server.mixins.json / authcore.client.mixins.json
βββ bungee.yml, velocity-plugin.json # proxy metadata (same jar)
βββ assets/authcore/ # icon + bundled translations
4. Managing multiple Minecraft versions
4.1 Version groups
One jar per range (not per exact version), because intermediary/mapping eras break silently at the edges:
| Group | Jar range | Built @ | Verified on | Mappings era |
|---|---|---|---|---|
| G1 | 1.16.0 β 1.18.2 | 1.18.2 | 1.16.5, 1.17.1, 1.18.2 | intermediary |
| G2 | 1.19.0 β 1.21.11 | 1.21.11 | 1.19.4, 1.20.6, 1.21.11 | intermediary |
| G3 | 26.1 β 26.2 | 26.2 | 26.1.2, 26.2 | unobfuscated (mojmap runtime) |
4.2 Why the ranges are where they are
The runtime namespace and method signatures drift between these eras. Concrete examples found while building the range jars (each one crashed a real server until handled):
ServerCommandSource.getPlayer(), intermediary idmethod_9207(1.16β1.18) vsmethod_44023(1.19.4+) β resolved viaCompat.sourcePlayer()(reflection over the candidate ids; also works on unobfuscated 26.1-26.2 via the mojmap name).ActionResultconstants, on 1.20.5+ they became classes with nested subclasses and the 1.21.11 mappings type them asActionResult$Passetc. A direct field reference compiled for 1.21.11 fails to load on 1.20.6 βCompat.actionResultPass()/Fail()resolve by field name ("PASS"/"FAIL", or stable intermediary idsfield_5811/field_5814).ScreenHandler.clicked,(β¦ClickTypeβ¦)LItemStack;on 1.16,(β¦ClickTypeβ¦)Von 1.17β1.21,(β¦ContainerInputβ¦)Von 26.1-26.2 β two mixins with descriptor-based selectors, eachrequire = 0, so exactly one matches per runtime.ServerPlayer.setGameMode, returnsvoidon 1.16,booleanon 1.17+ β two@Injecthandlers (oneCallbackInfo, oneCallbackInfoReturnable), descriptor-gated.CommandManager.execute,(ServerCommandSource, String)I(1.16β1.18),(ParseResults, String)I(1.19.4),(ParseResults, String)V(1.20.6+) β three mixins with stable-intermediary descriptors (method_9249,remap = false).- fabric-api callback interfaces are compiled per version and reference
version-specific types β registered reflectively in
FabricHooks(UseBlock/UseEntity/AttackEntity/UseItem, damage/death events) so no direct class linkage exists in the jar.
4.3 Stonecutter conditionals
Code that differs between versions is written with preprocessor comments. The template
files are kept in the active (1.21.11) output state, the branch that matches 1.21.11
is active code, every other branch is /*...*/-commented:
@Inject(
method =
/*? if < 1.19.4 {*/
/*"clicked(IILnet/minecraft/world/inventory/ClickType;Lnet/minecraft/world/entity/player/Player;)V",
*//*?} else {*/
"clicked(IILnet/minecraft/world/inventory/ContainerInput;Lnet/minecraft/world/entity/player/Player;)V",
/*?}*/
Supported: if, else if, else, version comparisons (< 1.19.4, >= 1.20.5, = 26.2),
loader constants (fabric, forge, neoforge, forgeLike). Never put conditionals
inside JSON metadata, Loom parses fabric.mod.json at configure time; use the
${placeholder} expansion from modSettings.variableReplacements instead.
4.4 Discipline (what keeps the promise alive)
- Never call an MC API whose intermediary id or signature is known to drift, go through
Compator use a conditional. - Mixins that may not exist on a version get descriptor selectors +
require = 0(one shape per era), never a bare method name withdefaultRequire = 1. - fabric-api callbacks are registered reflectively, never linked directly.
- Every range jar is booted on every range endpoint by the harness before release.
5. Managing multiple loaders
- Loader constants are registered automatically by Stonecraft, usable as
/*? if fabric {*/in code and asmod.isFabricetc. in the build script. - Each loader has its own thin entrypoint (delegating to the loader-neutral
net.ded3ec.AuthCoreServer.start()):- Fabric:
src/fabric/java/net/ded3ec/entrypoint/FabricEntry.java(DedicatedServerModInitializer), listed infabric.mod.json. - Forge:
src/main/java/net/ded3ec/entrypoint/ForgeEntry.java(1.16 β 1.20, classicEVENT_BUS.registerevent bus) +ForgeEntryModern.java(1.21+, record-based event bus),@Modclasses listed inMETA-INF/mods.toml. - NeoForge:
src/neoforge/java/net/ded3ec/entrypoint/NeoForgeEntry.java(1.20.1 β 26.x),@Modclass listed inMETA-INF/neoforge.mods.toml.
- Fabric:
- Mixin configs are declared per loader in the respective metadata
(
mixinsin fabric.mod.json,MixinConfigsentry in the forge/neoforge toml). - Loader-specific logic (e.g. fabric-api event registration in
Registry/FabricHooks) is gated with/*? if fabric {*/; forge-like loaders register the equivalents in their entrypoints on the Forge/NeoForge event buses (commands, join/leave, server tick). - The proxy plugin side (
proxy/package) is loader-agnostic, the same jar doubles as a Bungee/Velocity plugin viabungee.yml+velocity-plugin.json. - Known loader gaps (documented honestly): block/item-use interaction restrictions and
the Velocity modern-identity receiver are Fabric-only today (see
NeoForgeEntry); the loader-neutral mixins already cover lobby restrictions, handshake forwarding and chat on every loader.
Adding a loader for a group = add the loader to mc(...) in settings.gradle.kts,
provide versions/dependencies/<mc>.properties pins, add the loader metadata + entrypoint,
gate any loader-specific code, then boot-test with the harness.
6. Testing
6.1 Unit / security tests (tools/security-tests)
Standalone checks (password hashing, tokens, rate limiting, constant-time comparisons,
lockouts) run with pwsh tools/security-tests/run-tests.ps1.
6.2 Host-compatibility harness (tools/host-tests)
Boots real servers in Docker (JetBrains Runtime, Temurin fallback) for every groupΓloaderΓverify-version combination and asserts real behavior:
- mod loads, banner printed, no crash markers, security summary printed
- admin console commands work (
/authcore reload, list players, list online/offline, validate, backup creates a file, maintenance on/off) - config files + SQLite database created
- web admin panel: 401 without token, 401 with a bad token, 200 with
Bearertoken, 429 brute-force lockout - honeypot listener + detection log, game port listening, graceful
stopshutdown
Matrix in versions.json (range/build/loaders/verify/jbrMajor/scanPattern); runner
run-host-tests.ps1 (pwsh 7) with -Groups/-Range, -Loader, -Version,
-VerifyOverride, -ScanStable, -Smoke, -Jar, -Parallel (default 6), -NoLiveLogs,
-KeepReports and friends; live console log streaming, cached Docker images,
markdown/HTML/JSON reports under reports/ (reports/latest.* always points at the
newest run, with a boot-time chart + check-coverage heatmap in the HTML).
CI integration: .github/workflows/ci.yml runs the harness smoke on every push/PR and
the full range-endpoint matrix on schedule; .github/workflows/compat-scan.yml (weekly)
probes the Fabric meta + NeoForge/Forge maven for new stables, boots them via -ScanStable,
and automatically creates a v<mod>-<mc> tag + GitHub Release with a generated changelog
entry when a new version is detected and validated.
Current status: 8/8 range endpoints + 7/7 loader build targets PASS (1.16.5 ... 26.2 Γ fabric/forge/neoforge), 2026-08-10.
6.3 MFA / SSO verification
- 2FA: TOTP (RFC 6238) + single-use recovery codes + optional email OTP (10-min TTL, hashed, attempt-limited) + MFA step-up for sensitive commands.
- SSO: Redis-published session tickets (
authcore:sso:<uuid>), constant-time verification on join,trustVanillafallback. Every Redis call degrades to a no-op when Redis is off.
7. Roadmap
- P3 GameTest deep suite (register/login flows, lobby restrictions, 2FA) + automated mapping-stability check (intermediary-id diff between range endpoints) in CI.
- P4 Modrinth / CurseForge publish automation.