Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

[*.{yml,yaml,json}]
indent_size = 2

[*.md]
trim_trailing_whitespace = false
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Changelog

All notable changes to `laranail/authkit` are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Breaking.** Renamed from `laranail/auth-kit` to
`laranail/authkit`, and the namespace moved to `Simtabi\Laranail\AuthKit\`. The family now shares one root
namespace with each sibling as a segment under it.
- **Breaking.** Every public name is vendor-scoped. Laravel keeps these in flat global maps, where
a second package claiming the same key silently replaces the first:

| Surface | Before | After |
|---|---|---|
| Config key | `auth-kit` | `laranail.authkit` |
| Config file | `config/auth-kit.php` | `config/laranail/authkit.php` |
| Publish tags | `auth-kit-config`, … | `laranail::authkit-*` |
| Env prefix | `AUTH_KIT_*` | `AUTHKIT_*` |


### Added

- A `NamingConventionTest` that asserts the public names against the **live registries** on a booted
application, rather than the provider source, so the guard survives a refactor.


### Fixed

- The user-model exception named the old package.

### Removed

- `composer.lock` is no longer tracked. A library's lock records a resolution consumers never use.
10 changes: 10 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Code of conduct

This project follows the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/),
version 2.1.

In short: be respectful, assume good faith, and focus on what is best for the project and the
people using it. Harassment of any kind is unacceptable.

Report unacceptable behaviour to **opensource@simtabi.com**. Reports are handled confidentially,
and the maintainers will respond in a way proportionate to the circumstances.
59 changes: 59 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Contributing

Thanks for helping improve `laranail/authkit`.

## Getting set up

`laranail/*` packages resolve through git rather than Packagist, so the repositories block in
`composer.json` is what makes `composer install` work. Then:

```bash
composer install
composer test
```

## What a change needs

- **Tests.** Every behavioural change carries one; a bug fix carries a test that fails before it.
- **Live-registry assertions for public names.** Anything registered into a Laravel registry —
config key, publish tag, view or translation namespace, command name, middleware alias — is
asserted against the booted application, never by grepping the provider. See
`tests/Feature/NamingConventionTest.php`. Grepping proves how the registration was written; it
does not prove what the framework ended up holding.
- **Style.** `composer format` runs Pint. CI checks it.
- **A CHANGELOG entry** under `## [Unreleased]`.

## Naming rules that are not negotiable

Laravel keeps these in flat, global maps. A second package claiming the same key does not collide
loudly — it silently replaces the first, and the damage surfaces far away as a missing view, a
missing translation, or a security control attached to nothing.

| Surface | Shape |
|---|---|
| Config key | `laranail.authkit` |
| Config file | `config/laranail/authkit.php` |
| Publish tag | `laranail::authkit-<suffix>` |
| View namespace | `laranail-authkit::<view>` |
| Translation namespace | `laranail-authkit::<key>` |
| Artisan command | `laranail::authkit.<command>` |
| Middleware alias | `laranail-authkit` |

The separators differ because each registry parses its key differently, and that is forced rather
than stylistic. Commands use `::` because Symfony resolves an exact name before splitting on `:`.
Middleware aliases must not, because Laravel does `explode(':', $name, 2)` to take parameters.
Blade prefixes must not, because the tag already spends `::` between prefix and component.

**No bare short aliases.** A `authkit:install`
alias hands back exactly the collision the namespaced name exists to prevent.

## Verify against a real application

The package test suite runs in an isolated Testbench app, which is not the same as a real one. A
change to routing, publishing, the installer, or anything that interacts with Fortify should also
be checked in the demo application at `laranail/demos/authkit`, which installs both packages the way
a consumer does.

## Security

Do not open a public issue for a security problem. See [SECURITY.md](SECURITY.md).
80 changes: 63 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# laranail/authkit

[![Packagist Version](https://img.shields.io/packagist/v/laranail/authkit.svg?style=flat-square)](https://packagist.org/packages/laranail/authkit)
[![Tests](https://img.shields.io/github/actions/workflow/status/laranail/authkit/tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/laranail/authkit/actions/workflows/tests.yml)
[![Static analysis](https://img.shields.io/github/actions/workflow/status/laranail/authkit/static.yml?branch=main&label=static%20analysis&style=flat-square)](https://github.com/laranail/authkit/actions/workflows/static.yml)
[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
Comment on lines +3 to +6

Headless authentication for Laravel 13+. No views, routes, or controllers.

> [!WARNING]
Expand All @@ -14,29 +19,56 @@ Headless authentication for Laravel 13+. No views, routes, or controllers.

PHP 8.4+ / Laravel 13.x

## Installation
## Install

`laranail/*` packages resolve through git, not Packagist. Add the repositories block to your
application's `composer.json` — Composer ignores a dependency's own `repositories`, so it must list
the whole transitive closure:

```json
"repositories": [
{ "type": "vcs", "url": "https://github.com/laranail/authkit.git" },
{ "type": "vcs", "url": "https://github.com/laranail/console.git" },
{ "type": "vcs", "url": "https://github.com/laranail/enumerator.git" },
{ "type": "vcs", "url": "https://github.com/laranail/package-tools.git" },
{ "type": "vcs", "url": "https://github.com/laranail/captcha.git" },
{ "type": "vcs", "url": "https://github.com/laranail/db-tools.git" }
]
```

Then:

```bash
composer require laranail/authkit
php artisan vendor:publish --tag=laranail::authkit-config
```

For a ready-made Blade UI, install `laranail/authkit-preset` instead.
See [installation](docs/installation.md) for the full walkthrough.

## <a name="documentation"></a>Documentation

Full documentation: <https://opensource.simtabi.com/documentation/laranail/authkit/>

### Guides

## Documentation
- [Installation](docs/installation.md) — requirements, the repositories block, publishing config
- [Getting started](docs/getting-started.md) — authenticate a user with the core alone
- [Configuration](docs/configuration.md) — guard, rate limits, Fortify features, social credentials
- [Architecture](docs/architecture.md) — layering, what is delegated to Fortify, the extension seams
- [Security](docs/security.md) — the guarantees this package makes and the ones it does not
- [Release](docs/release.md) — versioning, tagging, and ordering across the family

- [Registration](docs/registration.md)
- [Login](docs/login.md)
- [Password reset](docs/password-reset.md)
- [Profile management](docs/profile-management.md)
- [Password updates](docs/password-updates.md)
- [Email verification](docs/email-verification.md)
- [Social login](docs/social-login.md)
- [Passkeys](docs/passkeys.md)
- [API tokens](docs/api-tokens.md)
- [Configuration](docs/configuration.md)
- [Security](docs/security.md)
- [Testing](docs/testing.md)
### Reference

- [Login](docs/login.md) · [Registration](docs/registration.md) · [Logout](docs/logout.md)
- [Password reset](docs/password-reset.md) · [Password updates](docs/password-updates.md)
- [Profile management](docs/profile-management.md) · [Email verification](docs/email-verification.md)
- [Social login](docs/social-login.md) · [Passkeys](docs/passkeys.md) · [API tokens](docs/api-tokens.md)

### Project

- [Testing](docs/testing.md) — how the suite is arranged and what it does not cover
- [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md)

## Configuration

Expand Down Expand Up @@ -269,9 +301,23 @@ $result = app(SocialCallbackAction::class)->execute(
);
```

## Related packages
## Sister packages

| Package | Role |
|---|---|
| [`laranail/authkit`](https://github.com/laranail/authkit) | Headless core — actions, contracts, result objects, REST API |
| [`laranail/authkit-preset`](https://github.com/laranail/authkit-preset) | Blade scaffolding on top of the core |
| `laranail/authkit-sso` | SAML 2.0 and OIDC single sign-on |
| `laranail/authkit-oauth` | OAuth and social identity |
| `laranail/authkit-tenancy` | Multi-tenancy |
| `laranail/authkit-ldap` | LDAP and Active Directory |

The family shares one root namespace, `Simtabi\Laranail\AuthKit\`, with each sibling a segment
under it.

## Contributing and security

- `laranail/authkit-preset` — Blade views + routes for this package
See [CONTRIBUTING.md](CONTRIBUTING.md). Report security issues privately — [SECURITY.md](SECURITY.md).

## License

Expand Down
26 changes: 26 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Security policy

## Reporting a vulnerability

Report security issues privately to **opensource@simtabi.com**. Do not open a public issue.

Include the affected version, the steps to reproduce, and the impact you believe it has. You will
get an acknowledgement within three business days and an assessment within ten.

This package sits directly on the authentication path, so please err toward reporting. A finding
here may also affect its sibling, `laranail/authkit-preset`; say so if you think it does, and we will
coordinate the fix across the family rather than patching one package in isolation.

## Supported versions

The `main` line receives security fixes. Pre-1.0 releases are not separately maintained — upgrade
to the current line rather than expecting a backport.

## Scope

In scope: authentication bypass, privilege escalation, account takeover, credential or token
disclosure, session fixation, and any weakening of the rate limiting or bot protection this package
configures.

Out of scope: findings that require an already-compromised application key, and misconfiguration of
an application that this package documents correctly.
6 changes: 5 additions & 1 deletion docs/api-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ $result = app(IssueTokenForUser::class)->execute(

The consuming application's model must use Sanctum's `HasApiTokens` trait and its Sanctum migration must be installed. Auth Kit does not register API routes or decide when a token should be issued; authenticate and authorize the request first, then choose a token name and abilities appropriate to the client. Return the plain-text token only at issuance, never log it, and use Sanctum ability middleware plus token revocation for client lifecycle management.

For a ready-made API route set and Sanctum response handling, use `laranail/authkit-preset` and see its [API routes guide](../../authkit-preset/docs/api-routes.md).
For a ready-made API route set and Sanctum response handling, use `laranail/authkit-preset` and see its [API routes guide](../../authkit-preset/docs/api-routes.md).

---

[← Docs index](../README.md#documentation)
72 changes: 72 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Architecture

How the core is layered, what it delegates to Fortify, and the seams the sibling packages extend.

## The family

```
laranail/authkit Simtabi\Laranail\AuthKit\ this package — headless core
laranail/authkit-preset Simtabi\Laranail\AuthKit\Preset\ Blade scaffolding
laranail/authkit-sso Simtabi\Laranail\AuthKit\Sso\ SAML and OIDC
laranail/authkit-oauth Simtabi\Laranail\AuthKit\OAuth\ OAuth and social identity
laranail/authkit-tenancy Simtabi\Laranail\AuthKit\Tenancy\ multi-tenancy
laranail/authkit-ldap Simtabi\Laranail\AuthKit\Ldap\ LDAP and Active Directory
```

The family shares one root namespace and each sibling is a segment under it. Two packages mapping
nested PSR-4 prefixes is fine: Composer's loader matches the longest prefix first, so
`Simtabi\Laranail\AuthKit\Preset\` resolves into the preset and everything shallower resolves
here.

## Layering

The intended shape is **form request → controller → action → service**, with DTOs in and result
objects out.

| Layer | Responsibility |
|---|---|
| Form request | Validation and authorization. Overridable by swapping the request, without touching the action. |
| Controller | HTTP shape only — resolve, delegate, respond. |
| Action | One use case, orchestration only. Behind a contract, container-bound. |
| Service | Reusable domain logic and infrastructure adapters. Unit-testable with no HTTP. |

Being honest about the current state: the middle two are partly collapsed. Several actions build a
validator inline instead of taking a form request, and there are more actions than services. That
is a known gap, not the target.

## What is delegated

Password reset, email verification, profile and password updates, and passkey ceremonies are
Fortify's. This package supplies the actions Fortify calls — `CreateNewUser`, `ResetUserPassword`,
`UpdateUserPassword`, `UpdateUserProfileInformation` — and configures which features are on.

That is a deliberate trade: first-party, audited code for the flows where hand-rolling is most
dangerous, and this package's own actions where the shape matters more than the cryptography.

## Extension seams

The sibling packages exist only if these are real. Each is a container-bound contract:

| Seam | Purpose | Consumers |
|---|---|---|
| `ResolveIdentityInterface` | One linking-and-provisioning path for social, SAML, OIDC and directory sign-in, including the verified-identity guard | all |
| `IdentityProviderRegistryInterface` | A registry a sibling pushes a provider into, replacing a closed enum as the universe of providers | oauth, sso |
| `IssueTokenForUserInterface` | Token issuance, with ability scoping and expiry | all |
| `TenantResolverInterface` | Resolves the active tenant; identity tables carry a nullable tenant key | tenancy |
| `DirectoryResolverInterface` | Resolves and syncs a directory entry to a local user | ldap |
| `GuardProfileInterface` | A named guard profile — domain or prefix, redirects, features, user model | all |

**Sequencing rule.** Every one of these changes a published contract, so they land here before the
first sibling is written. A sub-package that has to edit the core to do its job is not extending
the core, it is forking it.

## Why social login refuses an unverified email

`ResolveSocialIdentity` will link an external identity to an existing local account only when the
provider asserts the email is verified. Without that check, any provider returning an
attacker-controlled address is an account-takeover path. The guard belongs in the shared resolution
seam so SAML, OIDC and LDAP inherit it rather than each re-deriving it.

---

[← Docs index](../README.md#documentation)
6 changes: 5 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ php artisan vendor:publish --tag=laranail::authkit-config

Set `AUTHKIT_GUARD` to the application's intended guard. The `laranail.authkit.fortify.features` list enables Fortify capabilities individually: `reset-passwords`, `update-profile-information`, `update-passwords`, `email-verification`, and `passkeys`. Removing an item prevents Auth Kit from enabling that capability; remove corresponding application routes and UI too.

Credential throttling is controlled by `AUTHKIT_RATE_LIMIT_MAX_ATTEMPTS` and `AUTHKIT_RATE_LIMIT_DECAY_MINUTES`, defaulting to five attempts per minute. Social credentials, callbacks, and enabled providers are configured in `laranail.authkit.social`; see [social login](social-login.md) for provider settings.
Credential throttling is controlled by `AUTHKIT_RATE_LIMIT_MAX_ATTEMPTS` and `AUTHKIT_RATE_LIMIT_DECAY_MINUTES`, defaulting to five attempts per minute. Social credentials, callbacks, and enabled providers are configured in `laranail.authkit.social`; see [social login](social-login.md) for provider settings.

---

[← Docs index](../README.md#documentation)
6 changes: 5 additions & 1 deletion docs/email-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ Auth Kit provides headless controller bases for the verification lifecycle:

The application owns route registration, presentation, redirects, and mail configuration. The user model must implement Laravel's `MustVerifyEmail` contract for verification notifications and state changes to apply. Use Laravel's signed URL and `verified` middleware on the verification endpoint and routes that require a verified address. A profile email change for such a user clears its verification timestamp and sends a new notification.

Social login has its own verified-email rules. See [Social login](social-login.md) before enabling automatic social-account linking.
Social login has its own verified-email rules. See [Social login](social-login.md) before enabling automatic social-account linking.

---

[← Docs index](../README.md#documentation)
Loading