Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Telegram Mini Apps
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

BACKEND

Auth and sessions

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

Make account pages yours

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.

Choose an auth provider

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.

Sign in with email and password

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

Password managers save and fill the sign-in page

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)

Verify passkeys, SAML, LDAP and Ethereum sign-in

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.

Add a second factor

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.

Send a code and ask for it back

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.

List and revoke sessions

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 one

Copy code to clipboard

Each DVSession has its device, when it was created and last seen, and whether it is the current one.

Use the generated account pages

/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: 14

Copy 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

Send credentials to an API on another origin

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.

Status

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.

PREVIOUS Backend functions A function in lib/backend is an endpoint
NEXT Authorization Policies under DV.Auth.authorization
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom