Authyo Flutter Plugin
A Flutter plugin for integrating Authyo β a powerful multi-channel MFA service supporting WhatsApp, SMS, Email, and Call for OTP verification.
This plugin enables developers to implement OTP-based authentication for both legacy systems and modern, passwordless solutions with minimal configuration.
π Features
- π± Multi-channel support (WhatsApp, SMS, Email, Call)
- π§© Plug-and-play OTP UI (or bring your own)
- π§Ύ Works for both new and legacy systems
- π Configurable channel priority (no redeploy needed)
- π JWT-based login for new systems
- π§ REST API Support
- π Transparent billing & usage dashboard
- π Free trial for all new accounts
- π€ Affiliate support built-in
π§ Installation
Add the dependency in your pubspec.yaml:
dependencies:
authyo_flutter: <latest_version>
Run:
flutter pub get
π οΈ Usage
1. Legacy Systems (UI Provided)
2. Modern Systems (Passwordless)
Process
π οΈ Getting Started
1. Install
Add to your pubspec.yaml:
dependencies:
authyo_plugin: ^1.0.0 //Latest versions may vary
Import in your Dart code:
import 'package:authyo_plugin/authyo_plugin.dart';
2. Initialize
Important:
You must initializeAuthyoServicewith yourclientIdandclientSecretbefore making any requests.
//Get AuthyoService Instance
final AuthyoService authyoService = AuthyoService.instance;
//Initialize AuthyoService using your Authyo credentials
authyoService.init(clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET");
π¦ Usage
π Send OTP
// Call sendOtp function using AuthyoService
AuthyoResult otpResult = await authyoService.sendOtp(
ctx: context,
to: β+1234567890β, // or βuser@email.comβ
otpLength: 6, // optional, defaults to 6
expiry: 180, // optional, in seconds
authWay: AuthwayEnum.SMS, // optional, can be SMS, Whatsapp, Email, Voice
onVerificationComplete: (authyoResult) {
// This callback is optional and only necessary if plugin's default OTP verification dialog is used.
// If custom UI for OTP verification is used, you can omit this callback.
// Check for result.
if (authyoResult.result?.error == null) {
// Verification successful.
}
});
bool success = otpResult.result?.data?.results?.firstWhere((element) => element.success == true, orElse: () => Results(success: false)).success;
if (success == true) {
print("β
OTP Sent: ${otpResult.result?.message}");
} else {
print("β Error: ${otpResult.error?.message}");
}
π‘οΈ Verify OTP
// Call verifyOtp function using AuthyoService instance
AuthyoResult otpResult = await AuthyoService.instance.verifyOtp(maskId: "Mask ID",otp: "Received OTP");
if(otpResult.result!=null){
print("Success: ${otpResult.result?.message}");
}
else{
print("Error: ${otpResult.error?.message}");
}
π¨ Design styles & dashboard customization
The built-in dialog can be rendered in seven design languages. The look is resolved in this order (later wins):
- Built-in defaults (Flat, Authyo blue)
- Your Authyo dashboard β design style, colours, logo, button shape, layout, resend timer, enabled channels and social logins are fetched once (
GET /api/v1/authyoclient/mobileconfig), cached on device and refreshed in the background. - Overrides passed to
init()/sendOtp()
AuthyoDesignStyle |
Look |
|---|---|
flat (default) |
Solid colours, crisp borders β the 1.0.x look |
flat2 |
Flat with soft elevation |
minimalism |
Monochrome, underline input, thin outlines |
skeuomorphism |
Glossy gradients, bevels, recessed input |
claymorphism |
Pastel, puffy, large radii |
glassmorphism |
Frosted translucent card over a blurred backdrop |
neumorphism |
Extruded / pressed-in surfaces on one background colour |
authyoService.init(
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
// Optional β pin a style instead of following the dashboard:
designStyle: AuthyoDesignStyle.glassmorphism,
// Optional β partial theme override (anything unset comes from the dashboard / defaults):
theme: const AuthyoTheme(primary: Color(0xFF0EA5E9), hideBranding: true),
);
// Per-call override:
await authyoService.sendOtp(ctx: context, to: '+1234567890', designStyle: AuthyoDesignStyle.claymorphism);
// Or change at runtime:
authyoService.setDesignStyle(AuthyoDesignStyle.neumorphism);
authyoService.setDesignStyle(null); // follow the dashboard again
AuthyoTheme fields map 1:1 to the dashboard customization: buttonColor, buttonTextColor, inputBackground, inputBorder, inputText, smButton* (social buttons), headerText/bodyText + colours, logoUrl, hideBranding, resendButtonTimer, buttonStyle (rectangular / rounded / pill), smButtonStyle β socialButtonShape (social buttons: rectangular / rounded / pill-circle), smButtonSize β socialButtonsFullWidth (small icons / full-width rows with names), mainLayout (left / centre / right) and socialMediaLayout (top / bottom).
Reading the remote config yourself
final cfg = await authyoService.loadConfig(); // cached β instant
cfg.designStyle; // AuthyoDesignStyle? (null if the dashboard has none)
cfg.authMethods; // ["Sms", "Whatsapp", "Email"]
cfg.socialLogins; // [AuthyoSocialLogin(provider: google, id: "123"), ...]
cfg.otpLength;
Showing the dialog for an OTP you already sent
final res = await authyoService.requestOtp(to: '+1234567890', authWay: AuthwayEnum.sms); // no UI
await authyoService.showVerificationDialog(context, sendResult: res, to: '+1234567890',
onVerificationComplete: (r) { /* ... */ });
π Social login (Google, Microsoft, GitHub, LinkedIn)
Providers you enable on the dashboard (Application β Social Media Login) appear automatically in the dialog. Tapping one opens the provider in the system browser; the Authyo callback redirects to authyo://oauth/callback and the result is delivered to onVerificationComplete with the session JWT in result.data?.token.
You can also trigger it without the dialog:
final cfg = await authyoService.loadConfig();
final result = await authyoService.socialLogin(cfg.socialLogins.first);
πͺ Same flow as the web widget
The dialog behaves exactly like the Authyo web widget (auth-sdk.js) on the same application:
| Step | Web widget | Flutter dialog |
|---|---|---|
| Top-left back button | returns to the identity form | closes the dialog (your form is behind it) |
| "OTP sent to: β¦ Change" | rebuilds the identity form | closes the dialog |
| OTP entry | one box per digit, auto-advance, paste fills all | same (otpLength boxes) |
| Verify | automatic on the last digit (no button) | same |
| Wrong code | red Invalid OTP. Please try again. under the boxes, boxes cleared |
same |
| Countdown | OTP expires in m:ss + draining bar (blue β amber β red) from the server's expireTime |
same; falls back to the dashboard resend timer |
| Attempts | N of M attempts remaining pill, updated after every wrong code; lockout notice when it hits 0 |
same (AuthyoResult.attempts); on lockout the dialog closes and onVerificationComplete gets the failure |
| Resend cooldown | bar + You can resend in Ns, Resend OTP disabled until it ends (dashboard resend timer, min 60s) |
same; resends on the channel the code went out on |
| After expiry | red notice + Resend (email) / Sms Β· Whatsapp Β· VoiceCall (phone, only enabled channels) | same, styled as primary buttons |
| Resend | passes the dashboard resend timer as the new expiry | same (expiry) |
| Social logins | icon row under an OR divider (or on top) |
same |
| Success | green ring + check + "Verified successfully" inside the card, ticked icon next to the identity | same, then the dialog closes and onVerificationComplete fires |
| Logo | dashboard logo at 140px, else the Authyo logo from the server | same (server image, bundled copy only as offline fallback) |
| Footer | Terms & Privacy Policy links, "Powered by" + Authyo logo | same (hideBranding hides the second line) |
| Hide OTP | digits masked | same (isOtpHide) |
| Remember me | checkbox shown only when enabled on the dashboard; ticked β longer session token + identity prefilled next visit | same: checkbox in the dialog, rememberMe sent on verify, identity kept on device β read it with authyoService.rememberedIdentity() to prefill your form, clear with forgetIdentity(). Off β no checkbox, nothing sent, nothing stored |
One-time platform setup (required for social login)
Android β add to android/app/src/main/AndroidManifest.xml inside <application>:
<activity android:name="com.linusu.flutter_web_auth_2.CallbackActivity" android:exported="true">
<intent-filter android:label="authyo_oauth_callback">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="authyo" />
</intent-filter>
</activity>
iOS β add to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>CFBundleURLName</key><string>io.authyo.oauth</string>
<key>CFBundleURLSchemes</key><array><string>authyo</string></array>
</dict>
</array>
Using your own native sign-in SDKs instead
authyoService.init(
clientId: ..., clientSecret: ...,
onSocialLogin: (login, authUrl) async {
// e.g. run google_sign_in here, or open authUrl yourself and
// return AuthyoService.parseSocialCallback(callbackUri, login);
},
);
π API Reference
Methods
init
init({required String clientId, required String clientSecret, Duration? connectTimeout, Duration? receiveTimeout,
bool? showVerificationDialog, AuthyoDesignStyle? designStyle, AuthyoTheme? theme,
AuthyoSocialLoginHandler? onSocialLogin, bool prefetchConfig = true})
sendOtp
Future<AuthyoResult> sendOtp({
required String to,
int otpLength = 6,
int? expiry,
AuthwayEnum? authWay,
void Function(AuthyoResult authyoResult)? onVerificationComplete,
AuthyoDesignStyle? designStyle,
AuthyoTheme? theme,
});
- to: Phone number or email (required)
- otpLength: Length of OTP (optional, defaults to 6)
- expiry: OTP expiry in seconds (optional)
- authWay: Channel to send OTP (optional, defaults to dashboard preference)
- onVerificationComplete: Optional callback function which provide if authentication is successful or not. Only required when plugin's default OTP verification dialog is being used.
- designStyle / theme: Optional per-call overrides of the dialog look (see Design styles).
Returns: AuthyoResult
verifyOtp
Future<AuthyoResult> verifyOtp({required String maskId, required String otp})
- maskId: Received from
sendOtpresponse (required) - otp: The OTP entered by the user (required)
Returns: AuthyoResult
Pointing at another server
authyoService.init(clientId: ..., clientSecret: ..., baseUrl: 'https://staging.authyo.io');
Other methods
requestOtp({to, expiry, otpLength, authWay})β send an OTP with no UI.showVerificationDialog(context, {sendResult, to, onVerificationComplete, designStyle, theme, otpLength})β open the dialog for an already-sent OTP.loadConfig({forceRefresh})/currentConfig/clearConfigCache()β dashboard configuration.setDesignStyle(style)/setTheme(theme)/setShowVerificationDialog(bool)β runtime switches.socialLogin(AuthyoSocialLogin)β run a social provider sign-in.
AuthyoResult
A wrapper for API responses.
class AuthyoResult {
final AuthyoResponseModel? result;
final AuthyoError? error;
AuthyoResult.success(this.result) : error = null;
AuthyoResult.failure(this.error) : result = null;
}
AuthyoBaseResponse
For sendOtp:
{
"success": true,
"message": "submited successfully",
"data": {
"isTried": 1,
"isSent": 1,
"results": [
{ "success": true,
"message": "message submitted successfully",
"to": "919898******",
"authtype": "WHATSAPP",
"maskId": "36eeb3a16fAaAab49b48de0d729b9a35",
"createdTime": 1747312374,
"expiretime": 1747312434
}
]
}
}
For verifyOtp:
{
"success": true,
"message": "OTP Verified Successfully",
"status": "verified",
"data": {
"tokenType": "Bearer",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJGYTh6bE92N2FMUTdRcEl6enVkQnh3R0VxMVhTZUFCalRvcXdjcE0zM2w4PSIsImlzcyI6Imh0dHBzOi8vYXV0aHlvLmlvIiwiYXVkIjoiYWVhNWRhYzBlMjNDhiZmJjYzhjMTdmMDM2ZDlhZjgiLCJpYXQiOiIxNzUxOTA0MzkyIiwiZXhwIjoiMTc1MTkwNDY5MiIsInVzZXJJZCI6IkFZLUZhOHpsT3Y3YUxRN1FwSXp6dWRCeHdHRXExWFNlQUJqVG9xd2NwTTMzbDg9IiwicGhvbmUiOiI5MTc0MDUwODkwOTkifQ.6U3gFxTqSTuHNtsA77pRfvo8i9f0wfgvqbWb088v8Lg",
"expiresIn": 300,
"user": {
"phone": "9174050*****",
"userId": "AY-Fa8zlOv7aLQ7QpIzzudBGEq1XSeABjToqwcpM33l8="
}
}
}
β Error Handling
All errors are subclasses of AuthyoError, including:
BadRequestError(400)UnauthorizedError(401)ForbiddenError(403)NotFoundError(404)TooManyRequestsError(429)InternalServerError(500)ServiceUnavailableError(503)NetworkErrorTimeoutErrorUnknownApiError
π Documentation
Read the full documentation for implementation steps, API tokens, dashboard configuration, and more.
π§ͺ Try It Free
Sign up at authyo.io and get free credits to test MFA in your app.
π§ Affiliate Program
Promote Authyo and earn revenue. Details on your Authyo Dashboard.
π« Support
For issues, feature requests, or contributions, file a GitHub issue or contact support via authyo.io.
Libraries
- authyo_plugin
- The AuthyoPlugin to make your app's password-less authentications smooth and easy
Use AuthyoService to initialize your API client, set clientId and clientSecret,
and perform
sendOtpandverifyOtpcalls. Response is returned as AuthyoResult class. - authyo_plugin_method_channel
- authyo_plugin_platform_interface
- colors/app_color
- component/verification_controller
- component/verification_dialog
- constants/endpoints
- design/authyo_design_style
- design/authyo_style_factory
- design/authyo_theme
- design/styles/authyo_style
- design/styles/clay_style
- design/styles/flat2_style
- design/styles/flat_style
- design/styles/glass_style
- design/styles/minimal_style
- design/styles/neumorphic_style
- design/styles/skeuomorphic_style
- design/verified_badge
- handlers/utils
- models/base_response_model
- models/send_otp_input_params
- models/verify_otp_input_params
- models/widget_config