@open-core/identity
@open-core/identity is currently outdated and is not being actively maintained.
The OpenCore team is currently focusing maintenance on the framework, adapters, and CLI only. Identity may lag behind the current framework API and is not recommended for new projects at this time.
Enterprise-grade identity, authentication, and authorization plugin for the OpenCore Framework.
Identity gives your server a unified way to:
- authenticate players (multi-strategy),
- resolve principals (roles/ranks/permissions),
- and enforce authorization rules, while staying fully compatible with OpenCore’s dependency injection and lifecycle.
Install it with:
pnpm add @open-core/identity
Features
- Multi-Strategy Authentication
Supports
local,credentials, andapiauthentication modes. - RBAC with Rank + Permissions Principals can be resolved from static roles, a database, or an external API.
- Permission Overrides Account-level permission overrides support grants, revocations, and wildcard rules.
- Constructor Injection (DI) Identity services are available through OpenCore’s container with zero manual wiring.
- Stateless Persistence via Contracts
Storage is implemented through
IdentityStore(required) andRoleStore(optional depending on mode). - Boot Hooks Wait for external dependencies and run setup logic once Identity is ready.
Quick Start
Use constructor injection to consume Identity services.
import { Server } from "@open-core/framework";
import { AccountService } from "@open-core/identity";
@Controller()
export class AdminController {
constructor(private readonly accounts: AccountService) {}
@OnNet("admin:ban")
async ban(player: Player, targetId: string) {
await this.accounts.ban(targetId, { reason: "Policy violation" });
}
}
Installation
1) Register Stores
Identity requires an IdentityStore. Some principal modes also require a RoleStore.
import { Identity } from "@open-core/identity";
Identity.setIdentityStore(MyIdentityStore);
// Optional (only when principal.mode = "db"):
Identity.setRoleStore(MyRoleStore);
2) Install Identity
import { Identity } from "@open-core/identity";
Identity.install({
auth: { mode: "local", autoCreate: true, primaryIdentifier: "license" },
principal: {
mode: "roles",
defaultRole: "user",
roles: {
admin: { name: "admin", rank: 100, permissions: ["*"] },
user: { name: "user", rank: 0, permissions: ["chat.use"] },
},
},
});
Configuration Overview
Identity is configured through two main blocks:
auth: how players are authenticatedprincipal: how roles/rank/permissions are resolved
Public API
Identity exports a small set of high-level components:
Identity: installation and store registrationAuthService: authentication/registration entrypointAccountService,RoleService: business servicesIdentityStore,RoleStore: persistence contractsIDENTITY_OPTIONS: DI token for advanced container usage- Relevant types/interfaces for accounts, principals, and roles
Authentication Modes
auth.mode defines how players are identified and authenticated on connection.
Identity supports three authentication modes:
local: authenticate using FiveM identifiers (license/steam/discord/etc.)credentials: authenticate with username/password stored in your backendapi: delegate authentication to an external HTTP service
All modes are consumed through AuthService, which selects the correct provider based on configuration.
local (Default)
Authenticate using player identifiers (license/steam/discord/etc.). Ideal for classic FiveM servers where players simply join and play.
Workflow
- Player connects.
- Identity reads the
primaryIdentifierfrom the player identifiers. - IdentityStore is queried for an account matching that identifier.
- If missing and
autoCreate=true, a new account is created and linked.
Identity.install({
auth: {
mode: "local",
primaryIdentifier: "license",
autoCreate: true,
},
});
credentials
Username/password authentication stored in your system (passwords are hashed with bcrypt).
Workflow
- Player registers via your event/command/UI.
- Credentials are stored (hashed).
- Player logs in using credentials on next sessions.
Identity.install({
auth: { mode: "credentials" },
});
api
Delegates auth to your external HTTP service. Useful for networks with centralized accounts/SSO.
Workflow
- Identity sends identifiers + credentials to your API.
- Your API returns
{ success, accountId, account?, isNewAccount? }. - Identity links the returned account to the player.
Identity.install({
auth: {
mode: "api",
primaryIdentifier: "license",
api: {
baseUrl: "https://auth.example.com",
authPath: "/auth",
registerPath: "/register",
sessionPath: "/session",
logoutPath: "/logout",
},
},
});
API Payload (Minimal Shape)
Request:
{
"action": "authenticate",
"accountId": null,
"primaryIdentifier": "license:abc123",
"identifiers": [{ "type": "license", "value": "license:abc123" }],
"credentials": { "username": "john", "password": "123" }
}
Response:
{
"success": true,
"accountId": "42",
"isNewAccount": false
}
Principal Modes (Authorization)
principal.mode defines how Identity resolves the player's principal (role/rank/permissions).
Identity supports:
roles: static roles defined in codedb: roles loaded from your database usingRoleStoreapi: roles resolved by an external HTTP service
roles (Static)
Roles are defined in your Identity.install() options.
Best for
- predefined staff/user roles
- fast setup
- minimal infrastructure
Identity.install({
principal: {
mode: "roles",
defaultRole: "user",
roles: {
admin: { name: "admin", rank: 100, permissions: ["*"], displayName: "Staff" },
user: { name: "user", rank: 0, permissions: ["chat.use"], displayName: "Player" }
},
cacheTtl: 600000
},
});
db (Dynamic)
Roles are resolved from a database through RoleStore. Identity caches results using cacheTtl.
Setup
- Register
RoleStoreviaIdentity.setRoleStore(). - Use
principal.mode = "db".
Identity.setRoleStore(MyRoleStore);
Identity.install({
principal: { mode: "db", defaultRole: "1" }
});
api (External)
Principal resolution is delegated to your API. Your API returns principal data such as rank, permissions, and optional metadata.
Identity.install({
principal: {
mode: "api",
api: {
baseUrl: "https://api.mynetwork.com",
principalPath: "/v1/game/principal"
}
}
});
Permission Resolution Rules
Identity merges permissions using a strict priority:
- Explicit Revocation:
-permissiondenies access even if the role grants it. - Explicit Grant:
+permissionorpermissiongrants access. - Wildcard Override:
*grants access. - Role Permissions: falls back to role permissions (supports
*).
This allows precise per-account overrides without modifying global roles.
Implementing Contracts (Stores)
Identity is stateless by design: persistence is implemented through contracts.
You must implement:
IdentityStore(required): accounts, identifiers, bans, credential lookupsRoleStore(optional): only required whenprincipal.mode = "db"
IdentityStore (Required)
IdentityStore is responsible for loading and persisting account state.
Common required methods
findByIdentifier(identifier)findByLinkedId(linkedId)findByUsername(username)(used in credentials mode)create(data)update(id, data)setBan(id, banned, reason?, expiresAt?)
Example Skeleton
import { IdentityStore, IdentityAccount } from "@open-core/identity";
export class MyIdentityStore extends IdentityStore {
async findByIdentifier(identifier: string): Promise<IdentityAccount | null> { /* ... */ return null; }
async findByLinkedId(linkedId: string): Promise<IdentityAccount | null> { /* ... */ return null; }
async findByUsername(username: string): Promise<IdentityAccount | null> { /* ... */ return null; }
async create(data: any): Promise<IdentityAccount> { /* ... */ throw new Error("not implemented"); }
async update(id: any, data: any): Promise<void> { /* ... */ }
async setBan(id: any, banned: boolean, reason?: string, expiresAt?: Date): Promise<void> { /* ... */ }
}
Register Before Install
import { Identity } from "@open-core/identity";
Identity.setIdentityStore(MyIdentityStore);
Identity.install({
auth: { mode: "local" },
principal: { mode: "roles", roles: { user: { name: "user", rank: 0, permissions: [] } } }
});
RoleStore (Only for principal.mode = db)
RoleStore loads roles dynamically.
Typical methods:
findById(id)findByRank(rank)getDefaultRole()- plus any save/delete operations you support in your backend
import { RoleStore } from "@open-core/identity";
export class MyRoleStore extends RoleStore {
async findById(id: any) { /* ... */ return null; }
async findByRank(rank: number) { /* ... */ return null; }
async getDefaultRole() { /* ... */ return { id: "user", name: "user", rank: 0, permissions: [] }; }
}
Register it before install:
Identity.setRoleStore(MyRoleStore);
Use Cases
This guide shows three common Identity setups and practical solutions for typical integration problems.
1) Traditional FiveM Server (local + roles)
Automatic login using identifiers, roles defined in code.
Identity.install({
auth: { mode: "local", autoCreate: true, primaryIdentifier: "license" },
principal: {
mode: "roles",
defaultRole: "user",
roles: {
admin: { name: "admin", rank: 100, permissions: ["*"], displayName: "Staff" },
user: { name: "user", rank: 0, permissions: ["chat.use"], displayName: "Player" }
}
}
});
2) Integrated Web Dashboard (api + api)
Authentication and permissions resolved by a central service.
Identity.install({
auth: {
mode: "api",
api: {
baseUrl: "https://api.mynetwork.com",
timeoutMs: 5000,
headers: { Authorization: "Bearer internal-secret" }
}
},
principal: {
mode: "api",
api: {
baseUrl: "https://api.mynetwork.com",
principalPath: "/v1/game/principal"
}
}
});
3) Persistent Database System (credentials + db)
Username/password accounts + roles stored in SQL.
Identity.setIdentityStore(MyIdentityStore);
Identity.setRoleStore(MyRoleStore);
Identity.install({
auth: { mode: "credentials" },
principal: { mode: "db", defaultRole: "1" }
});
Common Problems
A) Per-Account Permission Overrides
Grant a permission to a single player without changing their role:
await accountService.addCustomPermission(player.accountID, "teleport.self");
Revoke a permission even if their role grants it:
await accountService.addCustomPermission(player.accountID, "-chat.global");
B) Waiting for External Dependencies
Ensure Identity does not query storage before your database is ready:
Identity.install({
hooks: {
waitFor: [MyDatabase.connect()],
onReady: async () => {
console.log("Identity system ready!");
}
}
});
C) Refreshing Principal Data
When rank/permissions change, refresh the principal so checks fetch updated data:
await principalProvider.refreshPrincipal(player);