Ronak.MauiOtpKit
1.0.1
dotnet add package Ronak.MauiOtpKit --version 1.0.1
NuGet\Install-Package Ronak.MauiOtpKit -Version 1.0.1
<PackageReference Include="Ronak.MauiOtpKit" Version="1.0.1" />
<PackageVersion Include="Ronak.MauiOtpKit" Version="1.0.1" />
<PackageReference Include="Ronak.MauiOtpKit" />
paket add Ronak.MauiOtpKit --version 1.0.1
#r "nuget: Ronak.MauiOtpKit, 1.0.1"
#:package Ronak.MauiOtpKit@1.0.1
#addin nuget:?package=Ronak.MauiOtpKit&version=1.0.1
#tool nuget:?package=Ronak.MauiOtpKit&version=1.0.1
MauiOtpKit ๐
Production-grade, cross-platform OTP solution for .NET MAUI
NuGet:
- https://www.nuget.org/packages/Ronak.MauiOtpKit
- https://www.nuget.org/packages/Ronak.MauiOtpKit.Core
๐ฏ Overview
MauiOtpKit provides a scalable, reusable OTP (One-Time Password) module for .NET MAUI applications. It handles:
- โ Android SMS auto-read via Google Play Services SMS Retriever API
- โ iOS OTP autofill support
- โ Robust OTP parsing with regex-based extraction
- โ Validation logic with expiry and retry limits
- โ Clean architecture with full DI support
- โ Multi-targeting for .NET 8, 9, and 10
- โ Thread-safe operations with proper async/await
- โ Extensible design for custom providers (Email, WhatsApp, etc.)
๐ฆ Features
Core Features
| Feature | Details |
|---|---|
| OTP Parsing | Regex-based extraction of 4-6 digit codes from any text |
| Validation | Match against expected code, enforce expiry and max attempts |
| Timer | Countdown timer with observable events for UI updates |
| State Management | Track OTP lifecycle (Idle โ Listening โ Received โ Validated) |
| Events | OTP detected, validation errors, expiry, max attempts exceeded |
| Logging | Full logging abstraction via Microsoft.Extensions.Logging |
Platform Support
Android
- SMS Retriever API (Google Play Services)
- Zero permissions required - No READ_SMS permission
- App-specific delivery - App hash ensures secure delivery
- Automatic detection - Works in background
iOS
- OTP Autofill - UITextContentType.OneTimeCode support
- Privacy-first - No SMS access (by design)
- Clipboard detection - Optional pasteboard integration
- User-driven - Notification or manual entry
๐ Quick Start
Installation
Install from NuGet:
dotnet add package Ronak.MauiOtpKit
dotnet add package Ronak.MauiOtpKit.Core
Configuration
In your MauiProgram.cs:
using MauiOtpKit.Extensions;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Add MauiOtpKit
builder.Services.AddMauiOtp(options =>
{
options.Length = 6; // OTP length
options.ExpirySeconds = 120; // 2 minutes
options.MaxAttempts = 3; // Max validation attempts
options.AutoStart = true; // Start on initialization
});
return builder.Build();
}
}
Basic Usage
// In your page/view model
private readonly IOtpService _otpService;
public YourPage()
{
_otpService = IPlatformApplication.Current?.Services
.GetRequiredService<IOtpService>();
// Subscribe to events
_otpService.OtpDetected += OnOtpDetected;
_otpService.ValidationError += OnValidationError;
}
protected override async void OnAppearing()
{
// Start OTP listening
await _otpService.StartAsync();
}
private void OnOtpDetected(object? sender, string otp)
{
// OTP automatically detected
OtpEntry.Text = otp;
}
private async void ValidateOtp()
{
var result = await _otpService.ValidateAsync(userInput);
if (result.IsSuccess)
{
await DisplayAlert("Success", "OTP validated!", "OK");
}
else
{
await DisplayAlert("Error", result.Message, "OK");
}
}
๏ฟฝ Documentation & Guides
For detailed setup and implementation guides, see:
- QUICKSTART.md - Get running in 5 minutes
- docs/ANDROID_SETUP.md - Android SMS Retriever configuration (app hash, backend setup, testing)
- docs/iOS_SETUP.md - iOS OTP autofill setup
- docs/ARCHITECTURE.md - Technical architecture & design patterns- docs/NUGET_PUBLISHING.md - Publish packages to NuGet.org (GitHub Actions workflow)- CHANGELOG.md - Feature list & version history
๏ฟฝ๐ง Android Setup
Step 1: Enable Google Play Services
Your app must be published on Google Play Store or use Firebase console for testing.
Step 2: Get App Hash
MauiOtpKit provides a helper to generate your app hash:
#if ANDROID
using MauiOtpKit.Platforms.Android;
var context = Android.App.Application.Context;
string appHash = AppHashHelper.GetAppHash(context);
Debug.WriteLine($"App Hash: {appHash}");
#endif
What is App Hash?
- 11-character unique identifier for your app
- Generated from app's package name + signing certificate
- Included in SMS messages received by SMS Retriever API
- Ensures only your app receives the OTP
Step 3: Configure Backend
Provide the app hash to your backend service. Backend should send SMS in format:
<#> Your OTP is 123456 ABC+D/EF+GHI
Where:
<#>- Required prefix123456- Your OTP codeABC+D/EF+GHI- Your app hash (11 chars, Base64 URL-safe)
Step 4: AndroidManifest.xml
No special permissions required! SMS Retriever API handles everything.
๐ iOS Setup
Step 1: Enable OTP Autofill
In your XAML Entry field:
<Entry
x:Name="OtpEntry"
Placeholder="000000"
Keyboard="Numeric"
MaxLength="6" />
MauiOtpKit automatically configures UITextContentType.OneTimeCode (equivalent in MAUI).
Step 2: Info.plist Configuration
No additional configuration needed. iOS handles OTP autofill natively.
Step 3: SMS Format
iOS expects SMS in standard format:
Your OTP is 123456
iOS will automatically detect and offer to autofill.
๐ API Reference
IOtpService
Main service orchestrating OTP flow:
public interface IOtpService
{
// Start/stop OTP listening
Task StartAsync(CancellationToken cancellationToken = default);
Task StopAsync();
// Validate user input
Task<OtpResult> ValidateAsync(string userInput);
// Reset state
Task ResetAsync();
// Get current state
OtpState GetState();
// Events
event EventHandler<string>? OtpDetected;
event EventHandler<string>? ValidationError;
event EventHandler? OtpExpired;
event EventHandler? MaxAttemptsExceeded;
}
OtpResult
Validation result:
public class OtpResult
{
public bool IsSuccess { get; set; }
public string? Message { get; set; }
public string? Code { get; set; }
public int AttemptCount { get; set; }
public int RemainingSeconds { get; set; }
}
OtpOptions
Configuration:
public class OtpOptions
{
public int Length { get; set; } = 6;
public int ExpirySeconds { get; set; } = 120;
public int MaxAttempts { get; set; } = 3;
public bool AutoStart { get; set; } = true;
public int TimeoutMilliseconds { get; set; } = 300000;
}
OtpState
public enum OtpState
{
Idle, // Not listening
Listening, // Waiting for OTP
Received, // OTP received but not validated
Validated, // Successfully validated
Expired, // OTP expired
Failed // Validation failed
}
๐๏ธ Architecture
Project Structure
MauiOtpKit/
โโโ MauiOtpKit.Core/ # Platform-agnostic business logic
โ โโโ Contracts/ # Interfaces
โ โโโ Models/ # OtpOptions, OtpResult
โ โโโ Implementations/ # Parser, Validator, Service, Timer
โ
โโโ MauiOtpKit/ # MAUI platform implementations
โ โโโ Extensions/ # DI setup
โ โโโ Platforms/
โ โ โโโ Android/ # SMS Retriever API
โ โ โโโ iOS/ # OTP Autofill
โ โ โโโ NullOtpReader.cs # Fallback
โ โโโ MauiOtpKit.csproj # Multi-target project
โ
โโโ samples/
โโโ MauiOtpKit.Sample/ # Example MAUI app
Core Components
1. IOtpReader (Platform-specific)
AndroidSmsReader- SMS Retriever API integrationIosOtpReader- Autofill supportNullOtpReader- Fallback
2. IOtpParser
- Regex-based extraction
- Supports various SMS formats
- Configurable OTP length
3. IOtpValidator
- Expiry checking
- Attempt limiting
- Code matching
4. IOtpTimer
- Countdown timer
- Per-second updates
- Expiry notifications
5. IOtpService
- Orchestrates full flow
- Thread-safe state management
- Event-driven architecture
๐ก Usage Examples
Example 1: Basic OTP Validation
// Subscribe to auto-detection
_otpService.OtpDetected += (sender, otp) =>
{
OtpEntry.Text = otp;
};
// Start listening
await _otpService.StartAsync();
// User enters OTP (or it's auto-filled)
var result = await _otpService.ValidateAsync(userInput);
if (result.IsSuccess)
{
// Success!
}
Example 2: Retry Logic
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries)
{
var result = await _otpService.ValidateAsync(userInput);
if (result.IsSuccess)
break;
retryCount++;
if (retryCount < maxRetries)
await _otpService.ResetAsync();
}
Example 3: Monitoring Expiry
_otpService.OtpExpired += async (sender, e) =>
{
await DisplayAlert("OTP Expired",
"Please request a new OTP code", "OK");
// Request new OTP from backend
await RequestNewOtp();
};
Example 4: Custom Configuration
builder.Services.AddMauiOtp(options =>
{
options.Length = 4; // Short OTP for tests
options.ExpirySeconds = 300; // 5 minutes
options.MaxAttempts = 5; // More attempts
options.AutoStart = false; // Manual control
});
๐ Security Considerations
Android
- โ No READ_SMS permission required
- โ App hash verification ensures app-specific delivery
- โ Google Play Services handles encryption
- โ Automatic cleanup after retrieval
iOS
- โ No SMS access (by iOS design)
- โ User-controlled via autofill
- โ Secure pasteboard handling
- โ Notification-based delivery
General
- โ Expiry enforcement prevents replay attacks
- โ Attempt limiting prevents brute force
- โ State validation ensures correct flow
- โ Thread-safe operations
๐งช Testing
Unit Testing
[TestClass]
public class OtpParserTests
{
private IOtpParser _parser;
[TestInitialize]
public void Setup()
{
_parser = new OtpParser(6);
}
[TestMethod]
public void Parse_ValidSms_ReturnsOtp()
{
var sms = "Your OTP is 123456";
var result = _parser.Parse(sms);
Assert.AreEqual("123456", result);
}
}
Mock Platform Readers
public class MockOtpReader : IOtpReader
{
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await Task.Delay(500);
OtpReceived?.Invoke(this, "123456");
}
public Task StopAsync() => Task.CompletedTask;
public event EventHandler<string>? OtpReceived;
public event EventHandler<string>? ErrorOccurred;
}
๐ Best Practices
1. Always Start/Stop
protected override async void OnAppearing()
{
await _otpService.StartAsync();
}
protected override async void OnDisappearing()
{
await _otpService.StopAsync();
}
2. Handle All Events
_otpService.OtpDetected += OnOtpDetected;
_otpService.ValidationError += OnValidationError;
_otpService.OtpExpired += OnOtpExpired;
_otpService.MaxAttemptsExceeded += OnMaxAttemptsExceeded;
3. Configure Appropriately
builder.Services.AddMauiOtp(options =>
{
options.ExpirySeconds = 120; // Don't make too long
options.MaxAttempts = 3; // Prevent brute force
options.Length = 6; // Standard length
});
4. Log Errors
builder.Logging.AddDebug();
// MauiOtpKit logs all operations
5. Reset on Failure
if (!result.IsSuccess)
{
await _otpService.ResetAsync();
// Optionally request new OTP
}
โ FAQ
Q: Why no READ_SMS permission on Android?
A: SMS Retriever API uses Google Play Services which handles SMS securely without exposing permission-requiring access. Google Play handles permission delegation internally.
Q: Can I use this for WhatsApp OTP?
A: Yes! Create a custom IOtpReader implementation for WhatsApp notification parsing.
Q: How do I get the app hash?
A: Use the provided AppHashHelper.GetAppHash(context) method. See Android Setup section.
Q: Does iOS allow SMS reading?
A: No. iOS restricts SMS access by design. Use OTP autofill (recommended) or ask users to paste from notification.
Q: Can I customize OTP parsing?
A: Yes! Implement IOtpParser with your own regex pattern.
Q: Is it thread-safe?
A: Yes! All operations use proper locking and are fully thread-safe.
Q: What's the minimum .NET version?
A: .NET 8.0 (Core) and .NET 8.0-android/ios (MAUI)
Q: How do I extend it?
A: Create custom implementations of IOtpReader, IOtpParser, IOtpValidator, or register different ones in DI.
๏ฟฝ Documentation
Complete documentation is available in the docs folder:
| Document | Purpose |
|---|---|
| QUICKSTART.md | Get started in 5 minutes |
| docs/ARCHITECTURE.md | Technical deep dive into design & components |
| docs/ANDROID_SETUP.md | Android SMS Retriever API setup guide |
| docs/iOS_SETUP.md | iOS OTP autofill configuration guide |
| CHANGELOG.md | Version history and feature list |
| IMPLEMENTATION_SUMMARY.md | Complete implementation details |
Recommended Reading Order:
- Start with QUICKSTART.md for basic setup
- Read platform-specific guides:
- docs/ANDROID_SETUP.md for Android implementation
- docs/iOS_SETUP.md for iOS implementation
- Review docs/ARCHITECTURE.md for technical details
- Check CHANGELOG.md for version information
๏ฟฝ๐ License
MIT License - See LICENSE file for details
--
๐ Advanced Topics
Custom OTP Provider
public class EmailOtpReader : IOtpReader
{
private readonly IEmailService _emailService;
public async Task StartAsync(CancellationToken cancellationToken = default)
{
var otp = await _emailService.GetOtpFromInboxAsync();
OtpReceived?.Invoke(this, otp);
}
public event EventHandler<string>? OtpReceived;
public event EventHandler<string>? ErrorOccurred;
}
// Register
services.AddSingleton<IOtpReader, EmailOtpReader>();
Conditional DI Registration
services.AddMauiOtp(options =>
{
#if DEBUG
options.ExpirySeconds = 3600; // 1 hour for testing
#else
options.ExpirySeconds = 120; // 2 minutes for production
#endif
});
Retry with Exponential Backoff
var retryPolicy = Policy
.Handle<InvalidOperationException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var result = await retryPolicy.ExecuteAsync(() =>
_otpService.ValidateAsync(userInput));
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 is compatible. net10.0-ios26.0 is compatible. |
-
net10.0-android36.0
- Microsoft.Extensions.Logging.Debug (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.20)
- Ronak.MauiOtpKit.Core (>= 1.0.1)
- Xamarin.GooglePlayServices.Auth (>= 121.0.0.2)
- Xamarin.GooglePlayServices.Basement (>= 118.3.0.2)
-
net10.0-ios26.0
- Microsoft.Extensions.Logging.Debug (>= 10.0.0)
- Microsoft.Maui.Controls (>= 10.0.20)
- Ronak.MauiOtpKit.Core (>= 1.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.