Search the site
BACKEND
Sign people in with email and password, add a second factor and let them manage their devices.
Account pages are generated for you, so you write none of the forms.
ON THIS PAGE
Make account pages yours
Choose an auth provider
Sign in with email and password
Password managers save and fill the sign-in page
Verify passkeys, SAML, LDAP and Ethereum sign-in
Add a second factor
Send a code and ask for it back
List and revoke sessions
Use the generated account pages
Send credentials to an API on another origin
Status
Prebuilt pages use your Material theme. Wide screens show a brand panel; small screens use a scrolling form. dartvel.pwa.name and icon supply the identity. dartvel.auth accepts tagline, heroImage and brandPanelColor (a six-digit RGB hex color). Include images in flutter.assets.
DV.Auth.appearance accepts DVAuthAppearance with a brandPanelBuilder for your own wide-screen panel. Sign-in and sign-up links follow configured account routes and carry the internal from destination. Pending requests disable submission and errors are announced.
There is no public password-reset endpoint yet. Interactive server HTML forms and visual parity before Flutter loads are not delivered by the current fallback renderer.
DV.Auth.configure(DVLocalAuthProvider());
DV.Notifications.mail.useProvider(DVMemoryMailProvider());Copy code to clipboard
DVLocalAuthProvider stores accounts in your app for development. DV.Auth.configure takes any DVAuthProvider.
try {
await DV.Auth.signInWithEmailAndPassword(email: email, password: password);
DV.log('signed in as ${DV.Auth.currentUser?.id}');
} on DVMfaRequired {
// The account has a second factor. Ask for the 6-digit code.
await DV.Auth.completeSecondFactor(code: await askForCode());
}Copy code to clipboard
DV.Auth.currentUser holds the signed-in DVAuthUser, or null.
An account with a second factor throws DVMfaRequired until completeSecondFactor succeeds.
DV.Auth also has signInWithProvider, signInWithPasskey and signInWithBiometrics. They need a DVAuthProvider that implements them: the generated backend's provider signs in with email and password and throws UnsupportedError for the others.
await DV.Auth.signUp(email: 'ada@example.com', password: 'a long passphrase');
final DVAuthUser? me = DV.Auth.currentUser;
await DV.Auth.signOut();Copy code to clipboard
The prebuilt sign-in and sign-up pages work with the browser's password manager, and with 1Password, Bitwarden, iCloud Keychain and Google Password Manager on phones.
The email and password fields are one form, marked as a username and a password, so a manager recognises them and fills them.
Enter in the password field signs in.
The manager is asked to save the password only after the server accepts it. A wrong password is never offered for saving, and leaving the page saves nothing.
Try it: if your browser has a saved password for any site, click the email field below. This demo signs nobody in, so it never asks to save.
Sign in (demo)
DVWebAuthn
Does: verifyAssertion checks a passkey signature against the stored credential
DVSaml
Does: validateResponse checks a SAML 2.0 response and its signature
DVLdapClient
Does: connect, then authenticate a user name and password against a directory
dvVerifySiwe
Does: Checks a Sign-In with Ethereum message, its signature, domain and nonce
These run on the server, where your backend functions call them.
CI runs the LDAP client against a real directory server.
DVLdapClient needs dart:io, so it is not available on the web.
final DVTotpEnrollment enrollment = await DV.Auth.enrollTotp();
// Show enrollment.uri as a QR code, then confirm with a code from the app.
await DV.Auth.confirmTotp(await askForCode());
final DVRecoveryCodes codes = await DV.Auth.regenerateRecoveryCodes();Copy code to clipboard
enrollTotp returns a secret and an otpauth:// URI for the QR code.
Nothing is active until confirmTotp accepts a code.
completeSecondFactor(recoveryCode: ...) signs in with a recovery code.
To ask for the factor before a page or a function, add mfa: DVMfa.required, or mfa: DVMfa.recent(Duration(minutes: 5)) for one entered in the last five minutes, to @DVPage or @DVBackendFunction. A generated call that is refused shows the challenge and sends the call again.
DV.Auth.code() mints the digits to send: six by default, from a secure random, with leading zeros kept.
// Six digits from a secure random, leading zeros kept.
final String code = DV.Auth.code();
// Or a longer one for something that matters more:
final String payoutCode = DV.Auth.code(length: 8);
await DV.Notifications.mail.send(DVMailMessage(
from: const DVMailAddress('hello@example.com', name: 'Shop'),
to: <DVMailAddress>[DVMailAddress(email)],
subject: 'Your sign-in code',
text: 'Type $code to sign in. It is good for ten minutes.',
));Copy code to clipboard
DV.Auth.askForCode asks for them back, over whatever is on screen. It answers with what was typed, or null when it was dismissed; verify checks the code without closing, so a wrong one can be typed again.
final String? typed = await DV.Auth.askForCode(
context,
message: 'We sent a code to ada@example.com.',
// Answer with what to tell them when it is wrong; null accepts it.
verify: (String code) async =>
await confirmCode(code) ? null : 'That code is wrong.',
);Copy code to clipboard
For a flow with a page of its own:
Functional
Class
@DVPage(title: 'Enter your code')
@pragma('vm:entry-point')
Widget _codePage(BuildContext context) => DV.Auth.AskForCodePage(
message: 'We sent a code to your address.',
onCode: (String code) async =>
await confirmCode(code) ? null : 'That code is wrong.',
);Copy code to clipboard
The field takes digits only, as many as the code has.
It offers the code a phone shows above the keyboard, through the one-time-code autofill hint.
final List<DVSession> sessions = await DV.Auth.sessions();
for (final DVSession session in sessions) {
DV.log('${session.device} last seen ${session.lastSeenAt}');
}
await DV.Auth.revoke(sessions.last.id); // sign one device out
final int signedOut = await DV.Auth.revokeOthers(); // every device but this oneCopy code to clipboard
Each DVSession has its device, when it was created and last seen, and whether it is the current one.
/login
Page: Sign in
/sign-up
Page: Sign up
/account/profile
Page: Profile
/account/security
Page: Password and second factor
/account/sessions
Page: Signed-in devices
/account/delete
Page: Delete the account
# pubspec.yaml
dartvel:
auth:
pages:
delete: false
signIn: /sign-in
deletionGraceDays: 14Copy code to clipboard
false leaves a page out. A path serves it somewhere else.
deletionGraceDays, 0 to 29, is how many days a deleted account waits before it is erased.
Every page except sign-up needs a session. Place one inside your own page with the DV.Auth widgets.
Functional
Class
@DVPage(title: 'Security')
Widget _securityPage(BuildContext context) => DV.Auth.SecurityPage();Copy code to clipboard
The session cookie is __Host-dv_session with SameSite=Lax. When the web app and the API are on different origins, allow both sides.
void allowApiOrigin() {
// Send the session cookie to your API when it is on another origin.
DVCredentialedOrigins.allowBackend('https://api.example.com');
}Copy code to clipboard
# pubspec.yaml
dartvel:
server:
cors:
origins: [https://app.example.com]
allowCredentials: true
methods: [GET, POST]Copy code to clipboard
With allowCredentials, a wildcard origin, method or header is refused.
The content-type and CSRF headers are allowed for you.
Native apps send a bearer token and need none of this.
Built
Spec section: Authentication
Partial
Spec section: Sessions and Account Management
Planned work and implementation limits
No avatar, and passkeys are not a second factor yet.
No per-tenant MFA policy or Redis session store.
session.location is never filled in.
FSL-1.1-MIT licensed. Built with Dartvel.
Dartvel is made by
To the bottom