Flutter SDK
One Dart package, two native bridges. Material widgets shipped in the box. Phone OTP, WhatsApp OTP, marketing attribution — all driving the same backend as the rest of QuickAuth.
Requirements
- Flutter 3.16+
- Dart SDK ≥ 3.0.0
- iOS 12.0+, Android minSdk 21
Install
flutter pub add quickauth_flutterInitialize
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:quickauth_flutter/quickauth_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await QuickAuth.init(
// In-app-safe. Issue it in the dashboard; it is scoped to OTP
// initiate/verify, app-lockable and rate-limited, so it ships in the binary.
publishableKey: 'pk_live_...',
// Single typed event stream — switch on the sealed AuthEvent subtypes.
onAuthEvent: (event) {
switch (event) {
case OtpSentEvent(): showOtpInput();
case OtpAutoReadEvent(): prefillInput(event.code);
case VerifiedEvent(): finishLogin(event.requestId);
case OtpFailedEvent(): showError(event.message);
case AuthErrorEvent(): showError(event.message);
}
},
);
runApp(const MyApp());
}Publishable key, or a session token
publishableKey needs no backend. It is accepted only on /v1/sdk/auth/**, scoped to OTP initiate and verify, rate-limited, and lockable to your registered app — so it is safe in a shipped binary in a way a client secret is not.
onTokenExpiry still works if you already mint session tokens server-side. Pass one or the other; passing both throws.
Consent
await QuickAuth.consent.set(true);
final granted = QuickAuth.consent.get(); // synchronous — returns bool, not FutureHeadless flow
Two methods. initiate kicks off the auth attempt; submitOtp hands back the user-typed code. All outcomes arrive on the onAuthEvent handler — including the OneTap silent re-auth path, where the backend emits VerifiedEvent directly without sending an SMS.
// Step 1 — kick off the attempt. Events arrive via onAuthEvent.
await QuickAuth.auth.initiate(
phone: '+919876543210',
channel: OtpChannel.auto,
);
// → onAuthEvent fires with one of:
// OtpSentEvent(sessionId, channel, expiresIn)
// VerifiedEvent(requestId, message) // OneTap fired
// AuthErrorEvent(code, message)
// Step 2 — when OtpSentEvent fires and the user types a code:
await QuickAuth.auth.submitOtp('123456');
// → onAuthEvent fires with VerifiedEvent / OtpFailedEvent / AuthErrorEvent
// Resend takes no arguments — it repeats the attempt already in flight, carrying its
// channel and autoSubmit. Passing a number again would start a second transaction and
// leave the user holding two codes, only one of which works.
await QuickAuth.auth.resendOtp();
// On user-initiated logout, drop the OneTap trust token:
await QuickAuth.auth.reset(forgetDevice: true);Pre-built widgets
import 'package:quickauth_flutter/quickauth_flutter.dart';
QuickAuthLoginButton(
phone: '+919876543210',
onSuccess: (jwt) => saveJwt(jwt),
onError: (err) => debugPrint(err.toString()),
)Auto-fill is native
iOS auto-fill is wired via UITextContentType.oneTimeCode in the native bridge. Android uses SMS Retriever — no permission prompt — and the bridge pushes the code into the Flutter widget over a platform stream.
WhatsApp auto-read 1.2.0
WhatsApp does not deliver zero-tap and one-tap codes over SMS. It broadcasts them to the app named in the template's supported_apps, which is why SMS Retriever never saw them. Android only — on iOS and WhatsApp Web there is no app to bind to, and those templates use copy-code instead.
// An auto-read code verifies itself, so the integration is initiate-and-listen.
await QuickAuth.auth.initiate(
phone: '+919876543210',
channel: OtpChannel.whatsapp,
autoSubmit: true,
);
// → OtpAutoReadEvent(code) then VerifiedEvent — no submitOtp() call needed.
// observeOTP() covers BOTH channels, so a merchant on OtpChannel.auto gets the
// same behaviour whichever way the code arrives.
QuickAuth.auth.observeOTP().listen(prefillInput);autoSubmit is off by default, deliberately
An app that already submits from its own observeOTP callback would otherwise submit twice, and the second attempt fails against a code the server has consumed — an error arriving after a success. Turn it on only if you are not submitting yourself.
WhatsApp OTP
// Opens wa.me with a prefilled message. Returns false if WhatsApp could not be opened.
final opened = await QuickAuth.whatsapp.open(
businessNumber: '+919574980048',
prefilledMessage: 'Hi, I want to log in.',
);
// Handle the return in your route observer or app_links callback:
await QuickAuth.attribution.captureLaunch(launchUri: uri);Attribution
// On cold launch (inside initState of your root widget):
await QuickAuth.attribution.captureLaunch();
// Track a conversion later:
await QuickAuth.attribution.trackConversion(
event: 'signup',
value: 0,
currency: 'INR',
);API reference
| Symbol | Description |
|---|---|
QuickAuth.init(...) | Async init in main(). Takes publishableKey or onTokenExpiry. |
QuickAuth.consent.set / get | Consent gate. get() is synchronous. |
.auth.initiate(phone:channel:autoSubmit:) | Begin an auth attempt. Emits OtpSentEvent or VerifiedEvent. |
.auth.resendOtp() | Repeat the attempt in flight. No arguments. Throws StateError if none. |
.auth.submitOtp(code) | Submit the user-typed code. Emits VerifiedEvent or OtpFailedEvent. |
.auth.reset(forgetDevice:) | Reset the state machine. Pass forgetDevice: true on logout. |
.auth.observeOTP() | Stream<String> of inbound OTPs — SMS and WhatsApp both. |
QuickAuth.whatsapp.open(...) | Open wa.me with a prefilled message. Returns false if it could not launch. |
QuickAuthLoginButton | Material login widget. |
QuickAuthOtpField | Material OTP input widget. |
QuickAuth.attribution.captureLaunch / trackConversion | Attribution APIs. |
Source & changelog
Source: github.com/quickauthin/quickauth-sdk-flutter. Package: quickauth_flutter on pub.dev.