decent-newsroom/identity-bundle is a reusable Symfony bundle for linking one host application user to one or more external identities.
The bundle's core job is account identity, not signing policy or publishing. It proves an incoming authentication request belongs to an external identity, resolves that identity to the host application's user through a small bridge contract, and stores durable identity links in identity_user_link.
Authorization: Nostr ... login requests.identity.provider service tag.App\Entity\User.DecentNewsroom\IdentityBundle\Entity\UserIdentityLink.Those boundaries let the host add more providers, such as email OTP, passkeys, or OAuth, without making each provider understand the host's user table.
| Area | Class |
|---|---|
| Bundle entry point | DecentNewsroom\IdentityBundle\IdentityBundle |
| Configuration | DependencyInjection\Configuration, IdentityExtension |
| Identity link entity | Entity\UserIdentityLink |
| Identity link repository | Repository\UserIdentityLinkRepository |
| Link management | Service\IdentityLinkingService |
| Nostr provider | Security\IdentityProvider\NostrIdentityProvider |
| Symfony authenticator | Security\Authenticator\NostrAuthenticator |
| Symfony user provider | Security\IdentityUserProvider |
| Signer strategy registry | Service\NostrSignerStrategyRegistry |
| NIP-46 session store | Service\Nostr\Nip46SessionStore |
| NIP-46 AUTH strategy | Service\Nostr\RemoteBunkerSignerStrategy |
IdentityOwnerInterface - implemented by the host user object. Exposes a stable opaque owner id and Symfony's UserInterface.UserRepositoryBridgeInterface - host adapter that finds or creates users by provider/external id, loads by owner id, and loads by Symfony identifier.IdentityProviderInterface - one authentication method, responsible for proving a request belongs to one provider-specific external id.SignerRelayProviderInterface - transitional host adapter for signer relay URLs used by legacy IdentityBundle NIP-46 code.Nip46AuthEventSignerInterface - transitional host adapter for server-side NIP-42 AUTH signing through an existing NIP-46 transport.NostrSignerStrategyInterface - strategy contract for server-side Nostr signing methods registered with identity.nostr_signer_strategy.# config/packages/identity.yaml
identity:
providers:
- nostr
email_otp:
code_length: 6
code_ttl_seconds: 600
max_attempts_per_hour: 5
Only the nostr provider is implemented in this package today. The email OTP options are reserved for the next provider implementation.
The extension also prepends Doctrine ORM mapping for the bundle entity, so the host does not need to manually register the entity path.
Install the package from the local path repository during extraction work:
{
"repositories": [
{
"type": "path",
"url": "packages/identity-bundle",
"options": {
"symlink": true
}
}
],
"require": {
"decent-newsroom/identity-bundle": "@dev"
}
}
Register the bundle if Symfony Flex has not done so:
use DecentNewsroom\IdentityBundle\IdentityBundle;
return [
IdentityBundle::class => ['all' => true],
];
Provide a bridge from IdentityBundle to the host user repository:
services:
DecentNewsroom\IdentityBundle\Contract\UserRepositoryBridgeInterface:
alias: App\Security\AppIdentityBridge
The host user class must implement IdentityOwnerInterface, usually by returning its primary key as a string from getIdentityOwnerId().
Wire the Nostr authenticator into the host firewall:
security:
firewalls:
main:
custom_authenticators:
- DecentNewsroom\IdentityBundle\Security\Authenticator\NostrAuthenticator
entry_point: DecentNewsroom\IdentityBundle\Security\Authenticator\NostrAuthenticator
Add the bundle migration namespace to the host Doctrine Migrations config:
doctrine_migrations:
migrations_paths:
IdentityBundleMigrations: '%kernel.project_dir%/vendor/decent-newsroom/identity-bundle/src/Migrations'
The package route file is intentionally empty at the moment. Nostr Connect QR and session routes live in SigningBundle in the current package layout.
/login with an Authorization header beginning with Nostr .NostrIdentityProvider decodes the base64 event, validates event shape, and delegates NIP-98 validation to nostr-kernel-bundle.nostr external id.NostrAuthenticator calls UserRepositoryBridgeInterface::findOrCreateByIdentity().Successful JSON login requests receive {"message":"Authentication Successful"}. Authentication failures and JSON entry-point responses return HTTP 401 JSON.
UserIdentityLink stores:
owner_id - opaque id for the host user;provider - provider machine name such as nostr;external_id - provider-specific unique identifier, such as a hex pubkey;The (provider, external_id) pair is unique, so one external identity cannot be claimed by two host users.
Focused package tests can run from the project root:
vendor/bin/phpunit packages/identity-bundle/tests
Project-level validation should still run inside Docker when the app container is available:
docker compose exec php bin/console lint:container
docker compose exec php bin/phpunit
How can I help you explore Laravel packages today?