Skip to content

Commit eefad16

Browse files
authored
Merge pull request #73 from devondragon/docs/registration-guard-spi
docs: document RegistrationGuard SPI demo (closes #58)
2 parents accfd1e + 783a9aa commit eefad16

1 file changed

Lines changed: 56 additions & 6 deletions

File tree

README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ This version uses:
7575
- WebAuthn/Passkey passwordless login (biometrics, security keys)
7676
- Passkey management (register, rename, delete)
7777
- OAuth2 login with Google, Facebook, and Keycloak
78+
- Multi-factor authentication (PASSWORD + WEBAUTHN passkey) via the `mfa` profile
79+
- Pluggable registration restrictions via the `RegistrationGuard` SPI (sample domain guard)
7880
- Role-based access control
7981
- CSRF protection
8082
- Security audit logging
@@ -292,12 +294,16 @@ For detailed API documentation, start the application and visit `/swagger-ui.htm
292294
293295
The application supports multiple configuration profiles:
294296
295-
| Profile | Purpose | Database | Use Case |
296-
| ----------------- | -------------------- | ------------------ | ------------------------------------ |
297-
| `local` | Local development | MariaDB/MySQL | Development with persistent database |
298-
| `test` | Testing | H2 (in-memory) | Automated testing |
299-
| `dev` | Development server | MariaDB/MySQL | Shared development environment |
300-
| `docker-keycloak` | Docker with Keycloak | MariaDB + Keycloak | OIDC authentication testing |
297+
| Profile | Purpose | Database | Use Case |
298+
| ------------------- | ----------------------------- | ------------------ | ------------------------------------------------- |
299+
| `local` | Local development | MariaDB/MySQL | Development with persistent database |
300+
| `test` | Testing | H2 (in-memory) | Automated testing |
301+
| `dev` | Development server | MariaDB/MySQL | Shared development environment |
302+
| `docker-keycloak` | Docker with Keycloak | MariaDB + Keycloak | OIDC authentication testing |
303+
| `mfa` | Multi-factor authentication | (combine w/ above) | Require PASSWORD + WEBAUTHN; e.g. `local,mfa` |
304+
| `registration-guard`| Restricted registration | (combine w/ above) | Domain-restricted sign-up demo; e.g. `local,registration-guard` |
305+
306+
> `mfa` and `registration-guard` are *opt-in add-on* profiles — activate them alongside a base profile (e.g. `--spring.profiles.active=local,registration-guard`). See [Registration Guard (restricting who can register)](#registration-guard-restricting-who-can-register) below.
301307
302308
### Quick Configuration Setup
303309
@@ -327,6 +333,50 @@ If you're running the application in a production-like environment, ensure you s
327333
328334
---
329335
336+
### Registration Guard (restricting who can register)
337+
338+
The Spring User Framework exposes a `RegistrationGuard` SPI that lets a consuming app allow or deny each registration attempt — useful for invite-code gating, email allowlists, or domain restrictions. The framework calls every `RegistrationGuard` bean for form, passwordless, and OAuth2/OIDC sign-ups; if any guard denies, registration is rejected with the guard's message.
339+
340+
This demo ships a sample implementation, [`DomainRegistrationGuard`](src/main/java/com/digitalsanctuary/spring/demo/registration/DomainRegistrationGuard.java), that restricts **form and passwordless** registration to a single email domain while allowing **all OAuth2/OIDC** registrations. It is gated behind the `registration-guard` Spring profile so the default demo experience is unaffected.
341+
342+
**Try it:**
343+
344+
```bash
345+
# Only @example.com email addresses can register via the form (OAuth2/OIDC still allowed)
346+
./gradlew bootRun --args='--spring.profiles.active=local,registration-guard'
347+
348+
# Override the allowed domain — pass it inside --args as a Spring Boot argument so it reaches the
349+
# forked application (a -D after the task sets it on the Gradle JVM only and is not forwarded)
350+
./gradlew bootRun \
351+
--args='--spring.profiles.active=local,registration-guard --registration.guard.allowed-domain=@mycompany.com'
352+
```
353+
354+
| Setting | Default | Purpose |
355+
| ------- | ------- | ------- |
356+
| `registration-guard` profile | off | Activates the sample guard bean |
357+
| `registration.guard.allowed-domain` | `@example.com` | Domain that form/passwordless registrations must match |
358+
359+
With the profile active, registering a non-matching email returns the friendly denial message `Registration is restricted to <domain> email addresses.`
360+
361+
**Writing your own guard:** implement `RegistrationGuard` as a Spring bean and return `RegistrationDecision.allow()` or `RegistrationDecision.deny(reason)`. The `RegistrationContext` exposes the email, `RegistrationSource` (FORM / PASSWORDLESS / OAUTH2 / OIDC), and provider name so you can apply different rules per source:
362+
363+
```java
364+
@Component
365+
public class InviteCodeGuard implements RegistrationGuard {
366+
@Override
367+
public RegistrationDecision evaluate(RegistrationContext context) {
368+
// e.g. look up an invite code carried on the request, check an allowlist, etc.
369+
return isInvited(context.email())
370+
? RegistrationDecision.allow()
371+
: RegistrationDecision.deny("An invitation is required to register.");
372+
}
373+
}
374+
```
375+
376+
Multiple guards compose — all must allow. See the framework's [Registration Guard documentation](https://github.com/devondragon/SpringUserFramework/blob/main/REGISTRATION-GUARD.md) for the full SPI reference.
377+
378+
---
379+
330380
#### **Mail Sending (SMTP)**
331381
The application requires an SMTP server for sending emails (e.g., account verification and password reset). Update the SMTP settings in your configuration file:
332382
```yaml

0 commit comments

Comments
 (0)