{"id":51101718,"url":"https://github.com/craftpulse/craft-auth-kit","last_synced_at":"2026-06-24T11:30:25.723Z","repository":{"id":365450126,"uuid":"1272121525","full_name":"craftpulse/craft-auth-kit","owner":"craftpulse","description":"Foundational authentication primitives for Craft CMS — passwordless tokens (magic links + email OTP), passkey wrappers, a recent-auth gate, and a password-validator contract.","archived":false,"fork":false,"pushed_at":"2026-06-17T11:31:28.000Z","size":40,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T12:20:44.954Z","etag":null,"topics":["authentication","craft","craft-plugin","craftcms","magic-link","otp","passkeys","passwordless","webauthn"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/craftpulse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-17T10:01:49.000Z","updated_at":"2026-06-17T11:31:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/craftpulse/craft-auth-kit","commit_stats":null,"previous_names":["craftpulse/craft-auth-kit"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/craftpulse/craft-auth-kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftpulse%2Fcraft-auth-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftpulse%2Fcraft-auth-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftpulse%2Fcraft-auth-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftpulse%2Fcraft-auth-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/craftpulse","download_url":"https://codeload.github.com/craftpulse/craft-auth-kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/craftpulse%2Fcraft-auth-kit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34731243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","craft","craft-plugin","craftcms","magic-link","otp","passkeys","passwordless","webauthn"],"created_at":"2026-06-24T11:30:23.766Z","updated_at":"2026-06-24T11:30:25.714Z","avatar_url":"https://github.com/craftpulse.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Auth Kit\n\nFoundational authentication primitives for Craft CMS 5 — passwordless tokens\n(magic links + email OTP), passkey wrappers, a recent-auth gate, and a\npassword-validator contract. The shared base for the CraftPulse security\necosystem.\n\nAuth Kit is **primitives + contracts**. It ships no routes, controllers, or\nUX — consuming plugins ([Warden](https://github.com/craftpulse/craft-warden),\nWarp, a Password Policy adapter) own those and call into Auth Kit's services.\nIt is a free, foundational plugin rather than a bare library because the token\nstore needs a table and migrations: a single installed plugin owns the\n`authkit_*` schema once, so consumers never collide on it.\n\n## Requirements\n\n- Craft CMS 5.3.0 or later\n- PHP 8.2 or later\n\n## Installation\n\n```sh\ncomposer require craftpulse/craft-auth-kit\n./craft plugin/install auth-kit\n```\n\nMost of the time you won't install Auth Kit directly — it is pulled in as a\nComposer dependency of the plugin that uses it.\n\n## What it provides\n\n### Token core — `AuthKit::$plugin-\u003etokens`\n\nIssue and consume hashed, single-use, TTL'd passwordless credentials. The raw\nsecret is never persisted (only its SHA-256 hash); consumption burns the token\natomically; issuance is enumeration- and timing-safe.\n\n```php\nuse craftpulse\\authkit\\AuthKit;\n\n$tokens = AuthKit::$plugin-\u003etokens;\n\n// Magic links — a 32-byte secret emailed as a verify URL, no attempt cap.\n$tokens-\u003eissueMagicLink($email, $returnUrl);     // bool — respond identically regardless\n$user = $tokens-\u003econsumeMagicLink($rawToken);    // ?User\n\n// Email OTP — short numeric code, attempt-capped, superseded on re-issue.\n$tokens-\u003eissueOtp($email);                        // bool\n$user = $tokens-\u003econsumeOtp($email, $code);       // ?User\n\n// Maintenance — prune expired rows (safe on a schedule).\n$tokens-\u003epurgeExpiredTokens();                    // int rows deleted\n```\n\nTunable as service properties (no settings model — set on the component, e.g.\nvia `config/app.php`): `tokenTtl` (default 900s), `otpDigits` (6),\n`otpMaxAttempts` (5), `perEmailLimit` (5), `perEmailWindow` (300s), and\n`magicLinkRoute` — the site route your plugin registers for the verify URL\n(Auth Kit imposes no URLs).\n\n\u003e [!IMPORTANT]\n\u003e `issueMagicLink()` / `issueOtp()` return whether a credential was actually\n\u003e sent, but any public-facing caller **must respond identically** whether or\n\u003e not the address exists — that is what keeps the endpoint enumeration-safe.\n\u003e Per-IP rate limiting belongs on your controller (core's `RateLimiter`); the\n\u003e per-address throttle here covers every channel including programmatic use.\n\n### Passkeys \u0026 recent-auth — `AuthKit::$plugin-\u003epasskeys`\n\nThin wrappers over core's WebAuthn machinery for front-end users, plus the\nrecent-auth gate (the passwordless replacement for elevated sessions; stamped\nautomatically on login).\n\n```php\n$passkeys = AuthKit::$plugin-\u003epasskeys;\n\n$passkeys-\u003egetCreationOptions($user);                  // string (JSON) for the browser\n$passkeys-\u003everifyCreation($credentials, $name);        // bool\n$passkeys-\u003egetPasskeys($user);                         // array\n$passkeys-\u003ehasPasskeys($user);                         // bool\n$passkeys-\u003edeletePasskey($user, $uid);\n\n$passkeys-\u003ehasRecentAuth($within);                     // bool — gate sensitive actions\n$passkeys-\u003estampRecentAuth();\n```\n\n### Password validation contract — `AuthKit::$plugin-\u003epasswords`\n\nThe neutral cooperation seam for password strength and breach checks. Plugins\ncooperate through this contract and a registry — **never** by sniffing each\nother with `isPluginInstalled()`.\n\n```php\n$result = AuthKit::$plugin-\u003epasswords-\u003evalidate($password, $user);\n\nif (!$result-\u003eisValid) {\n    // surface $result errors\n}\n```\n\nA provider (e.g. Password Policy) registers a validator implementing\n`craftpulse\\authkit\\passwords\\PasswordValidatorInterface`:\n\n```php\nuse craftpulse\\authkit\\services\\Passwords;\nuse craftpulse\\authkit\\events\\RegisterPasswordValidatorsEvent;\nuse yii\\base\\Event;\n\nEvent::on(\n    Passwords::class,\n    Passwords::EVENT_REGISTER_PASSWORD_VALIDATORS,\n    function(RegisterPasswordValidatorsEvent $event) {\n        $event-\u003evalidators[] = new MyPolicyValidator();\n    }\n);\n```\n\nIf no validator is registered, `validate()` is a graceful no-op (valid). The\ninterface is deliberately tiny and stable — treat any change to it as a major\nversion bump.\n\n### Front end\n\nA `craft.authKit` Twig variable exposes `hasPasskeys`, `passkeys`, and\n`webauthnJsUrl` for templates, and Auth Kit publishes a shared\n`authkit-webauthn.js` browser client. Default `auth_kit_magic_link` and\n`auth_kit_otp` system messages ship out of the box; consumers override them.\n\n## Events\n\n| Service | Event | Fired |\n|---|---|---|\n| `tokens` | `EVENT_AFTER_ISSUE_TOKEN` | after a token is issued |\n| `tokens` | `EVENT_BEFORE_CONSUME_TOKEN` | before consume (cancelable — refuses login, leaves the token unburned) |\n| `tokens` | `EVENT_AFTER_CONSUME_TOKEN` | after consume, user resolved |\n| `passwords` | `EVENT_REGISTER_PASSWORD_VALIDATORS` | to register password validators |\n\n## Consumers\n\n| Plugin | Uses Auth Kit for |\n|---|---|\n| **Warden** | Lite passwordless: magic links, passkeys, recent-auth |\n| **Warp** (planned) | the whole passwordless product surface |\n| **Password Policy** (planned adapter) | *provides* a `PasswordValidatorInterface` adapter |\n\n## License\n\n[MIT](LICENSE) — © CraftPulse\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraftpulse%2Fcraft-auth-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcraftpulse%2Fcraft-auth-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcraftpulse%2Fcraft-auth-kit/lists"}