{"slug": "meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1", "title": "Meet Polaris: A Complete PHP Identity Stack in One Line of Config (Part 1)", "summary": "A developer has built Polaris, an official identity module for the Univeros PHP framework, which provides a complete authentication stack with just one line of configuration. The module automatically registers 52 endpoints, including auth, users, and orgs routes, along with Cycle ORM entities, migrations, and middleware, eliminating the need for per-module bootstrapping. The developer emphasizes that the module's design prevents common auth code issues by decoupling it from the host application.", "body_md": "*This blog post series picks up where I left off in how AI agents revived my old PHP framework project.*\n\nIn my previous post, I wrote about how a swarm of AI agents helped me revive a PHP framework I had abandoned years ago. That post was the origin story. This little series is the first real proof that the revival was worth it.\n\nBecause a framework on its own is just plumbing. What I really wanted to know was simpler and scarier: could I build a serious, production-grade feature on top of it without hating my life? Not another to-do list demo. The thing every app needs, nobody enjoys writing, and that ruins your week when you get it wrong.\n\nAuthentication.\n\nSo I built [Polaris](https://polaris.univeros.io/). It is the official identity module for [Univeros](https://univeros.io/), and this post is the tour. The four how-tos that follow get hands-on with each piece. Here I just want to show you the shape of the thing and why it is built the way it is.\n\nI named it Polaris on purpose. Polaris is the fixed star sailors navigated by, and identity plays the same role in an application. Everything else eventually comes back to \"Who is this, and what are they allowed to do?\"\n\nInstall it:\n\n```\ncomposer require univeros/polaris\n```\n\nRegister it. This is the \"one line\" I keep bragging about:\n\n```\n// config/modules.php\nreturn [\n    new Univeros\\Polaris\\Module(),\n];\n```\n\nGive it the secrets it needs (from your environment or a secret manager, never from a file in the repo):\n\n```\nexport APP_KEY=\"…\"                                # 32-byte base64, seeds the peppers + encrypter\nexport AUTH_JWT_PRIVATE_KEY=\"$(cat private.pem)\"  # signs access tokens\nexport AUTH_JWT_PUBLIC_KEY=\"$(cat public.pem)\"    # verification + the JWKS endpoint\n```\n\nRun the migrations and confirm it is alive:\n\n```\nbin/altair db:migrate\nbin/altair routes:list --format=json | grep auth\nbin/altair doctor\n```\n\nThat is it. That single `new Module()`\n\ncontributes every `/auth`\n\n, `/users`\n\n, and `/orgs`\n\nroute, the Cycle ORM entities, the migrations, the authentication and authorization middleware, and the container bindings. Fifty-two endpoints, wired and ready, with zero per-module bootstrapping on your side.\n\nHere is something I learned the hard way over the years. Most auth code doesn’t die because the crypto is wrong, but because it is welded to the app that hosts it. You write a beautiful login flow for project A, then project B comes along and you spend two days surgically extracting it. By the time you’re done you’ve introduced three new bugs.\n\nUniveros has a module contract system, and Polaris leans on it completely. A module declares what it provides (routes, entities, migrations, middleware, container bindings) and the host wires it all in automatically. There is no \"now go register these fifteen services\" step:\n\n```\nfinal class Module implements\n    ModuleInterface,\n    RoutesProviderInterface,\n    MiddlewareProviderInterface,\n    EntityDirectoriesProviderInterface,\n    MigrationDirectoriesProviderInterface\n{\n    public function name(): string\n    {\n        return 'univeros/polaris';\n    }\n\n    public function apply(Container $container): void\n    {\n        $authConfig = AuthConfig::fromArray($this->authConfigArray());\n        $secrets = Secrets::fromEnvironment($this->environment());\n\n        $container->instance(AuthConfig::class, $authConfig);\n        $container->instance(Secrets::class, $secrets);\n\n        (new IdentityBindings())->apply($container);\n        (new TokenBindings())->apply($container, $authConfig, $secrets);\n        (new SessionBindings())->apply($container);\n        (new HttpBindings())->apply($container);\n        (new MfaBindings())->apply($container, $authConfig, $secrets);\n        (new OrganizationBindings())->apply($container);\n    }\n}\n```\n\nNotice that `apply()`\n\nbuilds the config and secrets eagerly. That is deliberate. Forget to set `AUTH_JWT_PRIVATE_KEY`\n\n, and the app does not boot with a quietly insecure fallback and bite you in production three weeks later. It fails immediately, with a clear message. Fail loud. Fail early.\n\nBefore diving into the how-tos, there’s one pattern worth knowing. Once you see it, the rest of the module reads the same way. Univeros uses an Action, Input, Domain, Responder shape (it is basically Action-Domain-Responder with a typed input DTO in front). Every endpoint is the same quad:\n\n```\nHTTP edge   Action            thin route target: declares input, responder, domain, required permissions\n            Input (readonly)  a typed request DTO with validation rules()\n            Responder         turns a Payload into JSON or an RFC 9457 Problem Details body\nDomain      *Service          the business logic: transactional, emits PSR-14 events\n            Contracts/*       ports: SmsSender, OtpMailer, PasswordHasher, Clock, and friends\nPersistence Entity/* (Cycle)  UUID-v7 entities mapped into the host ORM schema\nSecurity    token machinery   implements the framework's Altair\\Http auth contracts\n```\n\nThe domains stay thin. They validate the edge, call a service, and translate outcomes into HTTP. All the real work (hashing, lockout, token minting, event dispatch) lives in the services. That separation keeps the codebase from rotting, and it is why each how-to in this series can show you a real, unedited domain class that still fits on a single screen.\n\n**Authentication:** Register, email verification, password login, /auth/me, logout / logout-all (Part 2, coming soon)\n\n**Tokens:** Asymmetric JWT access tokens (RS256/EdDSA) plus opaque rotating refresh tokens with reuse detection; a JWKS endpoint (Part 2, coming soon)\n\n**Sessions:** Per-device session list, individual and global revocation (Part 2, coming soon)\n\n**MFA / OTP:** TOTP (QR), SMS OTP, email OTP, recovery codes, the login-MFA gate, step-up (Part 3, coming soon)\n\n**Passwords:** Argon2id, policy enforcement, breached-password hook, reset and change (Part 2, coming soon)\n\n**Multi-tenant RBAC:** Organizations, memberships, roles, permissions, invitations, org switching (Part 4, coming soon)\n\n**Authorization:** Declarative permission guard middleware plus a programmatic Gate (Part 4, coming soon)\n\n**Providers & events:** Pluggable SMS/email/breach ports, PSR-14 domain events (Part 5, coming soon)\n\nI am not going to wave my hands and say \"secure by design.\" Here is what that phrase actually cashes out to:\n\nPasswords hashed with Argon2id, transparently rehashed when parameters change, with timing-equalized verification.\n\nAccess tokens signed with asymmetric keys (RS256 or EdDSA) with kid-based rotation. Resource servers verify with the public key alone.\n\nRefresh tokens, OTP codes, recovery codes, verification tokens, and reset tokens are never stored in plaintext. They are hashed or kept as keyed HMACs.\n\nRotating refresh tokens with family-based reuse detection.\n\nRate limiting on every sensitive endpoint, sliding-window account lockout, and anti-enumeration on register, resend, and forgot-password.\n\nAn audit log fed by the event stream.\n\nThe standards it follows are the boring, correct ones: JWT (RFC 7519), JWKS (RFC 7517), TOTP (RFC 6238), OAuth 2.0 refresh semantics and the Security BCP (RFC 9700), Problem Details (RFC 9457), and OWASP ASVS for password storage. The full threat model lives in the [reference docs](https://polaris.univeros.io/docs/).\n\nThat was the whole point of this exercise. Part one of the Univeros series asked whether AI agents could resurrect a dead PHP framework. This series is the honest test: could I build something genuinely hard on top of it?\n\nI think the answer is yes, and the evidence is in the shape of the code. Polaris is fifty-two endpoints, full MFA, multi-tenant RBAC, rotating tokens with theft detection, and an audit trail, and yet every endpoint is the same readable quad. The framework's module system meant the whole thing installs in one line.\n\nThe agents helped a lot with the grind: the RFC 6238 test vectors, functional tests for fifty-two endpoints, keeping the docs in sync with code. But the design decisions (authority comes from the database, not the token; last-owner protection; fail-open breach checks) came from years of getting auth wrong and remembering the scars. The agents are fast hands. The judgment is still mine.\n\nPick the piece you need:\n\n**Part 2:** Logins that do not leak covers register, email verification, password login, JWT access tokens, and rotating refresh tokens with reuse detection. (coming soon)\n\n**Part 3:** Real MFA in an afternoon covers TOTP/QR, SMS, email, recovery codes, the login gate, and step-up. (coming soon)\n\n**Part 4:** One user, many orgs covers organizations, roles, permissions, the Gate, and the tenant invariants. (coming soon)\n\n**Part 5:** Bring your own providers and events covers the SMS/email/breach ports and the PSR-14 event stream. (coming soon)\n\nThe reference docs live at polaris.univeros.io. The framework is at univeros.io. The source is on [GitHub](https://github.com/univeros/polaris).\n\n```\ncomposer require univeros/polaris\n```\n\nOne line. A whole identity stack. The fixed star your app navigates by. See you in part 2.", "url": "https://wpnews.pro/news/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1", "canonical_source": "https://dev.to/2amtech/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1-lkc", "published_at": "2026-08-05 14:25:37+00:00", "updated_at": "2026-08-05 15:01:17.058620+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Polaris", "Univeros", "PHP", "Cycle ORM"], "alternates": {"html": "https://wpnews.pro/news/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1", "markdown": "https://wpnews.pro/news/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1.md", "text": "https://wpnews.pro/news/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1.txt", "jsonld": "https://wpnews.pro/news/meet-polaris-a-complete-php-identity-stack-in-one-line-of-config-part-1.jsonld"}}