<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Trí Đặng</title>
    <description>The latest articles on DEV Community by Trí Đặng (@tri_dev_dhm).</description>
    <link>https://dev.to/tri_dev_dhm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3492670%2F13abbe03-4613-4756-ba54-c51acab3dae4.png</url>
      <title>DEV Community: Trí Đặng</title>
      <link>https://dev.to/tri_dev_dhm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tri_dev_dhm"/>
    <language>en</language>
    <item>
      <title>Perfecting Monochrome Prints: A Dive into ZPL Image Dithering</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Thu, 26 Mar 2026 03:05:45 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/perfecting-monochrome-prints-a-dive-into-zpl-image-dithering-7bc</link>
      <guid>https://dev.to/tri_dev_dhm/perfecting-monochrome-prints-a-dive-into-zpl-image-dithering-7bc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq8ctoyxy42rnwo4y58y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq8ctoyxy42rnwo4y58y.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thermal barcode printers are incredible machines for printing sharp text and crisp barcodes. However, they possess one major limitation: they can only print pure black dots or leave the paper blank. There are no shades of gray.&lt;/p&gt;

&lt;p&gt;When you attempt to print a standard color photograph or a graphic with smooth gradients on a Zebra printer, the software must decide whether each pixel should be 100% black or 100% white. Historically, this conversion resulted in highly washed-out images with hard, jagged edges.&lt;/p&gt;

&lt;p&gt;Starting with flutter_zpl_generator v1.2.0, the ZplImage widget introduces native, high-performance Dithering Algorithms written entirely in Dart. This completely transforms how your images look on standard 203 DPI thermal printers.&lt;/p&gt;

&lt;p&gt;📸 The Visual Difference&lt;br&gt;
Here are the three algorithms in action side-by-side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F613y09xnef770b4y9ler.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F613y09xnef770b4y9ler.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By intelligently distributing the "error" of converting a pixel to black or white to its neighboring pixels, dithering creates the optical illusion of grayscale tones using only pure black dots.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Algorithms Explained
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ZplDitheringAlgorithm&lt;/code&gt; enum gives you three distinct modes to convert continuous-tone images into monochrome hexadecimal maps perfectly suited for the &lt;code&gt;~DG&lt;/code&gt; (Download Graphics) ZPL command.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Floyd-Steinberg (The New Default)
&lt;/h3&gt;

&lt;p&gt;This is the gold standard for natural image reproduction. Floyd-Steinberg smoothly diffuses quantization errors across adjacent pixels. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Photographs, smooth gradients, and natural lighting.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Extremely smooth optical illusions of gray tones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ZplImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;image:&lt;/span&gt; &lt;span class="n"&gt;avatarBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;ditheringAlgorithm:&lt;/span&gt; &lt;span class="n"&gt;ZplDitheringAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;floydSteinberg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Atkinson Dithering
&lt;/h3&gt;

&lt;p&gt;Developed by Bill Atkinson for the original Apple Macintosh, this algorithm diffuses only a fraction (3/4) of the quantization error. This prevents dots from clustering too heavily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Vintage graphics, high-contrast imagery, and detailed line art on lower resolution (203 DPI) printers.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: A distinct, high-contrast "newspaper print" dot pattern without the image completely washing out into darkness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ZplImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;image:&lt;/span&gt; &lt;span class="n"&gt;vintageArtBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;ditheringAlgorithm:&lt;/span&gt; &lt;span class="n"&gt;ZplDitheringAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;atkinson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Threshold (Legacy Mode)
&lt;/h3&gt;

&lt;p&gt;This is the simplest approach. If a pixel's luminance is over 50%, it becomes white; otherwise, it becomes black. No error diffusion is calculated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Pure black-and-white logos, icons, and vector-style graphics that should not have half-tone patterns.&lt;br&gt;
&lt;strong&gt;Result&lt;/strong&gt;: Complete, solid, hard separations between black and white.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ZplImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;image:&lt;/span&gt; &lt;span class="n"&gt;companyLogoBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;ditheringAlgorithm:&lt;/span&gt; &lt;span class="n"&gt;ZplDitheringAlgorithm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;Running image processing mathematically natively inside Dart allows Flutter apps to bypass bloated dependencies and offline backend converters. &lt;/p&gt;

&lt;p&gt;Our dithering algorithms are heavily optimized using raw &lt;code&gt;Float32List&lt;/code&gt; memory allocations and contiguous array looping, ensuring that converting even large graphical elements will generate the ZPL Hex map string in mere milliseconds securely and completely offline on the user's device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;ZplImage&lt;/code&gt; transitioning from naive thresholding to mathematically sound error-dispersion dithering, your receipts, shipping labels, and ID badges can finally feature beautiful, recognizable graphics printed entirely on standard thermal hardware.&lt;/p&gt;

&lt;p&gt;Happy printing!&lt;/p&gt;

</description>
      <category>zpl</category>
      <category>zebra</category>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Mastering RFID Smart Labels with flutter_zpl_generator</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Thu, 26 Mar 2026 02:43:51 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/mastering-rfid-smart-labels-with-flutterzplgenerator-1pk5</link>
      <guid>https://dev.to/tri_dev_dhm/mastering-rfid-smart-labels-with-flutterzplgenerator-1pk5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3chheixn72t70x0r9j72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3chheixn72t70x0r9j72.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have ever worked in logistics, warehousing, or retail, you know the limitations of traditional barcodes. They require a direct line of sight. They must be scanned one by one. They are prone to smudging and tearing.&lt;/p&gt;

&lt;p&gt;Radio-Frequency Identification (RFID) solves these problems entirely. With RFID, a single scanner can read hundreds of items simultaneously, straight through cardboard boxes and across fast-moving conveyor belts—no visual contact necessary. &lt;/p&gt;

&lt;p&gt;Modern Zebra printers, such as the ZT411 RFID, are incredible machines because they are dual-purpose. They print the visual information (like text and barcodes) using a thermal print head while simultaneously using a built-in radio antenna to encode data onto the tiny silicon microchip embedded inside the label. We call these "smart labels."&lt;/p&gt;

&lt;p&gt;Starting with version 1.2.0, the &lt;code&gt;flutter_zpl_generator&lt;/code&gt; package brings native support for this powerful print-and-encode workflow to Flutter. In this guide, we will explore the fundamentals of RFID encoding in ZPL and how you can implement it in your applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mechanics of Print-and-Encode
&lt;/h2&gt;

&lt;p&gt;An RFID label consists of a standard adhesive label with a microscopic chip and an antenna (an "inlay") hidden inside. When you send a print job to a Zebra RFID printer, two things happen at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Visual Printing&lt;/strong&gt;: The thermal print head burns the traditional ZPL commands (text, shapes, barcodes) onto the label surface.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Radio Encoding&lt;/strong&gt;: The printer's internal antenna sends a signal to power the inlay's microchip and burn data into its memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the Zebra Programming Language (ZPL), this radio encoding is controlled by two specific commands: &lt;code&gt;^RS&lt;/code&gt; (RFID Setup) and &lt;code&gt;^RF&lt;/code&gt; (RFID Read/Write Format).&lt;/p&gt;




&lt;h2&gt;
  
  
  Demystifying RFID Memory Banks
&lt;/h2&gt;

&lt;p&gt;Before writing data, it is crucial to understand where that data goes. The industry standard EPC Class 1 Gen 2 RFID tag contains four distinct memory banks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Bank 0 (Reserved)&lt;/strong&gt;: Used for storing the kill and access passwords.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Bank 1 (EPC)&lt;/strong&gt;: The Electronic Product Code. This is the primary identifier that scanners read as items move through a supply chain.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Bank 2 (TID)&lt;/strong&gt;: The Tag ID. This is a unique, permanently factory-burned identifier that cannot be changed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Bank 3 (User)&lt;/strong&gt;: Optional memory for custom application data (like manufacture dates or batch codes).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the vast majority of use cases, you will be interacting exclusively with the &lt;strong&gt;EPC&lt;/strong&gt; bank.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Implementation in Flutter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;flutter_zpl_generator&lt;/code&gt; package abstracts the complexity of raw ZPL strings into strongly typed Dart objects. Here is how you can build an RFID-enabled label.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hardware Initialization (&lt;code&gt;ZplRfidSetup&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Before reading or writing data, you must configure the printer for the specific type of RFID tags loaded in its tray. This is equivalent to the &lt;code&gt;^RS&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_zpl_generator/flutter_zpl_generator.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the printer for standard EPC Class 1 Gen 2 tags&lt;/span&gt;
&lt;span class="n"&gt;ZplRfidSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;tagType:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also specify advanced recovery actions. For instance, if the printer detects a defective chip, you can tell it to print a void pattern and try the next label automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ZplRfidSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;tagType:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;voidPrintLength:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Print a 50-dot void pattern on failure&lt;/span&gt;
  &lt;span class="nl"&gt;labelsPerForm:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// Attempt to print up to 2 labels before erroring&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Encoding the Payload (&lt;code&gt;ZplRfidWrite&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;To transfer data onto the chip, use the &lt;code&gt;ZplRfidWrite&lt;/code&gt; class. This maps to the &lt;code&gt;^RF&lt;/code&gt; command and supports all standard operations, from writing data to locking memory banks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ZplRfidWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'3034257BF461AD20'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Hexadecimal payload&lt;/span&gt;
  &lt;span class="nl"&gt;operation:&lt;/span&gt; &lt;span class="n"&gt;RfidOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// W&lt;/span&gt;
  &lt;span class="nl"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;RfidDataFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// H&lt;/span&gt;
  &lt;span class="nl"&gt;startingBlock:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;byteCount:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;// 8 bytes (16 hex characters)&lt;/span&gt;
  &lt;span class="nl"&gt;memoryBank:&lt;/span&gt; &lt;span class="n"&gt;RfidMemoryBank&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;epc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Assembling the Complete Smart Label
&lt;/h3&gt;

&lt;p&gt;Let us combine the visual and radio commands into a single &lt;code&gt;ZplGenerator&lt;/code&gt; pipeline. This will print a traditional shipping label and simultaneously encode its digital twin into the EPC bank.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZplGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;config:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ZplConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;printWidth:&lt;/span&gt; &lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nl"&gt;labelLength:&lt;/span&gt; &lt;span class="mi"&gt;609&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;printDensity:&lt;/span&gt; &lt;span class="n"&gt;ZplPrintDensity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;d8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;commands:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 1: Initialize RFID Hardware&lt;/span&gt;
    &lt;span class="n"&gt;ZplRfidSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;tagType:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Encode the EPC Bank&lt;/span&gt;
    &lt;span class="n"&gt;ZplRfidWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'DEADBEEF01020304'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;operation:&lt;/span&gt; &lt;span class="n"&gt;RfidOperation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;RfidDataFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;startingBlock:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;byteCount:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;memoryBank:&lt;/span&gt; &lt;span class="n"&gt;RfidMemoryBank&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;epc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 3: Print the visual components&lt;/span&gt;
    &lt;span class="n"&gt;ZplText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'WAREHOUSE SMART LABEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontHeight:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;fontWidth:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ZplBarcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'DEADBEEF01020304'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;type:&lt;/span&gt; &lt;span class="n"&gt;ZplBarcodeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;printInterpretationLine:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;zplString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// You can now send zplString to your printer via flutter_zpl_printer!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Native Safety: Hexadecimal Validation
&lt;/h2&gt;

&lt;p&gt;The most common point of failure in RFID workflows is providing malformed data to the printer. If you send a non-hexadecimal string to a printer expecting hex data, the printer will silently ignore the command, resulting in unencoded, "dead" tags entering your supply chain.&lt;/p&gt;

&lt;p&gt;To prevent this, &lt;code&gt;flutter_zpl_generator&lt;/code&gt; includes rigorous runtime validation. If you attempt to pass invalid characters while using &lt;code&gt;RfidDataFormat.hex&lt;/code&gt;, the package throws a descriptive &lt;code&gt;ArgumentError&lt;/code&gt; immediately locally, before the ZPL is even generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This code will throw an error and fail fast&lt;/span&gt;
&lt;span class="n"&gt;ZplRfidWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'NOT_A_HEX_STRING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;RfidDataFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toZpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Error: ZplRfidWrite: hex format requires valid hex characters (0-9, A-F) only, got "NOT_A_HEX_STRING"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  High-Performance Production with Templates
&lt;/h2&gt;

&lt;p&gt;If you are generating thousands of unique shipping labels, recalculating geometries and image dithering algorithms for every single label is highly inefficient. &lt;/p&gt;

&lt;p&gt;For high-volume operations, you should pair &lt;code&gt;ZplRfidWrite&lt;/code&gt; with the new &lt;code&gt;ZplTemplate&lt;/code&gt; engine. This allows you to compile the expensive parts of the label once, and synchronously inject dynamic EPC codes in a tight loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Define the layout with {{placeholders}}&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZplTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ZplGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;config:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ZplConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;printWidth:&lt;/span&gt; &lt;span class="mi"&gt;812&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;labelLength:&lt;/span&gt; &lt;span class="mi"&gt;609&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;commands:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;ZplRfidSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;tagType:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;ZplRfidWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'{{dynamic_epc}}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;RfidDataFormat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;startingBlock:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;byteCount:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;memoryBank:&lt;/span&gt; &lt;span class="n"&gt;RfidMemoryBank&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;epc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;ZplText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'Product: {{product_name}}'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;ZplBarcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'{{dynamic_epc}}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;type:&lt;/span&gt; &lt;span class="n"&gt;ZplBarcodeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code128&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Perform the heavy AST initialization once&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Blast dynamic data at sub-millisecond speeds&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;databaseRecords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Widget Alpha'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'epc'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'AABB11220000FF01'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Widget Beta'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s"&gt;'epc'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'AABB11220000FF02'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;databaseRecords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;fastZpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bindSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s"&gt;'product_name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'dynamic_epc'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'epc'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;printer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastZpl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Developer Best Practices
&lt;/h2&gt;

&lt;p&gt;As you integrate RFID into your logistics architecture, keep these considerations in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Default Tag Type&lt;/strong&gt;: EPC Class 1 Gen 2 (&lt;code&gt;tagType: 8&lt;/code&gt;) is the ubiquitous standard. Unless you have highly specialized legacy tags, this should always be your default.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Exact Byte Counting&lt;/strong&gt;: When supplying hexadecimal strings, ensure the string length perfectly aligns with your expected byte count. Each byte requires exactly two hex characters (e.g., 8 bytes = 16 characters).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Data Immutability&lt;/strong&gt;: If you need to ensure a tag is permanently tamper-proof once it leaves your facility, use &lt;code&gt;RfidOperation.writeWithLock&lt;/code&gt;. Be warned: this action is irreversible. You will not be able to rewrite the chip later.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Emulators vs. Hardware&lt;/strong&gt;: Remember that the Labelary API cannot simulate RFID encoding. The &lt;code&gt;^RS&lt;/code&gt; and &lt;code&gt;^RF&lt;/code&gt; commands execute entirely at the RF-antenna hardware level. While &lt;code&gt;flutter_zpl_generator&lt;/code&gt; ensures your syntax is flawlessly formatted, real-world testing must be conducted on physical Zebra devices (like the ZT411 series).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Radio-Frequency Identification is transforming inventory management from a line-of-sight challenge into a seamless, automated breeze. By combining the &lt;code&gt;flutter_zpl_generator&lt;/code&gt; package's new RFID commands with its robust templating engine, you can deploy enterprise-grade, high-volume smart label printing workflows directly from your Dart and Flutter applications.&lt;/p&gt;

</description>
      <category>rfid</category>
      <category>zpl</category>
      <category>printer</category>
      <category>zebra</category>
    </item>
    <item>
      <title>Beyond Authentication: Storing Secure Data with Passkey Large Blob</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Mon, 23 Mar 2026 17:02:02 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/beyond-authentication-storing-secure-data-with-passkey-large-blob-588b</link>
      <guid>https://dev.to/tri_dev_dhm/beyond-authentication-storing-secure-data-with-passkey-large-blob-588b</guid>
      <description>&lt;p&gt;What if your passkey was more than just a key? What if it was also a tiny, cryptographically secure "vault" that travels with you? &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Large Blob extension&lt;/strong&gt; in WebAuthn allows you to store and retrieve small amounts of arbitrary data (up to 1024 bytes) directly on the passkey hardware. This data is protected by the same biometric security as the passkey itself and is synchronizable across devices through services like iCloud Keychain or Google Password Manager.&lt;/p&gt;

&lt;p&gt;In this guide, we'll show you how to leverage the Large Blob feature in your Flutter apps using &lt;code&gt;flutter_passkey_service&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is the Large Blob Extension?
&lt;/h2&gt;

&lt;p&gt;Most WebAuthn extensions are designed for authentication metadata. &lt;strong&gt;Large Blob&lt;/strong&gt; is unique because it provides &lt;strong&gt;data storage&lt;/strong&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Key Use Cases:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Portable Settings&lt;/strong&gt;: Store a user's encryption salt, theme preference, or a small recovery token that stays with the passkey even if they delete the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Access&lt;/strong&gt;: Store a small offline-only JWT or a local "permission bit" that can be verified without a network connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary Secrets&lt;/strong&gt;: Save a small cryptographic seed that can be used to re-derive other local keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size&lt;/strong&gt;: Most authenticators are capped at &lt;strong&gt;1KB (1024 bytes)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware Support&lt;/strong&gt;: While growing, not all authenticators (especially older security keys) support Large Blob storage yet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Implementing Large Blob in Flutter
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;flutter_passkey_service&lt;/code&gt;, Large Blob management is integrated directly into the standard registration and authentication flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Enable Support during Registration
&lt;/h3&gt;

&lt;p&gt;To use Large Blob, you must first signal that you want to support it when the passkey is being created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;registerWithLargeBlob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'your-server-generated-challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpName:&lt;/span&gt; &lt;span class="s"&gt;'My Secure App'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="s"&gt;'user-123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;username:&lt;/span&gt; &lt;span class="s"&gt;'user@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;enableLargeBlob:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Enable support here&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify if the authenticator actually accepted the extension&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;supported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientExtensionResults&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;largeBlob&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;supported&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Passkey Created. Large Blob Supported: &lt;/span&gt;&lt;span class="si"&gt;$supported&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Writing Data during Authentication
&lt;/h3&gt;

&lt;p&gt;Writing to the Large Blob happens during an authentication attempt. You provide the bytes you want to save, and once the user verifies via Face ID/Touch ID, the data is committed to the authenticator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:typed_data'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;saveToPasskey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;dataToSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'My Secret Data'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;codeUnits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'your-server-generated-challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;largeBlobWrite:&lt;/span&gt; &lt;span class="n"&gt;dataToSave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Pass the bytes to write here&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// When the user authenticates, the data is written&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Data saved successfully to the passkey!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Reading Data during Authentication
&lt;/h3&gt;

&lt;p&gt;Retrieving the data is just as simple. During authentication, set &lt;code&gt;largeBlobRead: true&lt;/code&gt;, and the authenticator will return the stored bytes upon successful biometric verification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;readFromPasskey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAuthenticationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'your-server-generated-challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;largeBlobRead:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Set read to true&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract the blob from the results&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;storedBlob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientExtensionResults&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;largeBlob&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storedBlob&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromCharCodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storedBlob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Recovered Data: &lt;/span&gt;&lt;span class="si"&gt;$message&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'No data found in the Large Blob.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check for Support&lt;/strong&gt;: Since Large Blob is a hardware-dependent extension, always check &lt;code&gt;response.clientExtensionResults?.largeBlob?.supported&lt;/code&gt; after registration. If it returns false, you should have a fallback storage mechanism (like Secure Storage or your backend).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Encryption&lt;/strong&gt;: While the Large Blob is protected by biometrics, it is still "just bytes" on the authenticator. If you are storing highly sensitive information, consider encrypting the data &lt;em&gt;before&lt;/em&gt; writing it to the Large Blob (using a key derived via the &lt;strong&gt;PRF extension&lt;/strong&gt;!).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Size Management&lt;/strong&gt;: Keep your payloads lean. Stay well under the 1024-byte limit to ensure compatibility across as many different authenticators as possible.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Large Blob extension transforms passkeys from simple login tools into secure, portable data carriers. Whether you're storing user preferences or cryptographic salts, &lt;code&gt;flutter_passkey_service&lt;/code&gt; makes it easy to add this advanced WebAuthn capability to your Flutter application.&lt;/p&gt;

&lt;p&gt;Happy building!&lt;/p&gt;

</description>
      <category>passkey</category>
      <category>webauthn</category>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>Unlock Local Encryption with Passkeys: A Guide to the KEK (PRF) Feature</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Mon, 23 Mar 2026 15:14:36 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/unlock-local-encryption-with-passkeys-a-guide-to-the-kek-prf-feature-18ai</link>
      <guid>https://dev.to/tri_dev_dhm/unlock-local-encryption-with-passkeys-a-guide-to-the-kek-prf-feature-18ai</guid>
      <description>&lt;p&gt;Passkeys are revolutionizing the way we authenticate by providing a secure, passwordless experience. But did you know that passkeys can do more than just authenticate users with a server? With the &lt;strong&gt;Passkey PRF (Pseudo-Random Function) extension&lt;/strong&gt;, you can securely derive symmetric &lt;strong&gt;Key Encryption Keys (KEK)&lt;/strong&gt; directly from the passkey itself.&lt;/p&gt;

&lt;p&gt;In this article, we will explore what the KEK feature is, why it is incredibly useful, and how you can implement it in your Flutter app using the &lt;code&gt;flutter_passkey_service&lt;/code&gt; package.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is the KEK (PRF Extension) Feature?
&lt;/h2&gt;

&lt;p&gt;The PRF extension allows an authenticator (like Face ID, Touch ID, or a security key) to evaluate a pseudo-random function using a secret tied specifically to the credential. Simply put, it allows you to derive a symmetric encryption key from the passkey during the authentication process. &lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this useful?
&lt;/h3&gt;

&lt;p&gt;Typically, if you want to encrypt local data (like an offline game save, a local profile, or a secure offline wallet), you need a password to derive an encryption key. With the PRF extension, you can &lt;strong&gt;eliminate the local password&lt;/strong&gt; completely. &lt;/p&gt;

&lt;p&gt;The derived Key Encryption Key (KEK) is tied strictly to the user's passkey. This makes it perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encrypting local offline data&lt;/strong&gt; (e.g., game saves, private diaries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure local storage&lt;/strong&gt; without requiring a backend to hold encryption keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge architectures&lt;/strong&gt; where the server never sees the plaintext data or the encryption keys&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to Implement KEK Derivation in Flutter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;flutter_passkey_service&lt;/code&gt; makes it incredibly simple to integrate the PRF extension. The flow consists of two main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registering the passkey with PRF support enabled.&lt;/li&gt;
&lt;li&gt;Authenticating with a "salt" to derive the encryption key.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Enable PRF during Registration
&lt;/h3&gt;

&lt;p&gt;When you create a new passkey, you need to tell the authenticator that you intend to use the PRF extension for this credential. You do this by setting &lt;code&gt;enablePrf: true&lt;/code&gt; when generating the registration options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;registerPasskeyWithPrf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Generate options (usually done by communicating with your backend)&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createRegistrationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'your-server-generated-challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpName:&lt;/span&gt; &lt;span class="s"&gt;'My Secure App'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Must match verified domain&lt;/span&gt;
    &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="s"&gt;'user-123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;username:&lt;/span&gt; &lt;span class="s"&gt;'user@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;enablePrf:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- 1. Enable PRF here&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Perform registration&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional: You can verify if PRF is actually supported by the authenticator&lt;/span&gt;
  &lt;span class="c1"&gt;// final prfEnabled = result.clientExtensionResults?.prf?.enabled ?? false;&lt;/span&gt;

  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Passkey registered successfully! PRF Supported: true'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Authenticate and Derive the KEK
&lt;/h3&gt;

&lt;p&gt;When the user comes back to the app and you need the encryption key to decrypt their local data, you prompt an authentication process. &lt;/p&gt;

&lt;p&gt;You must provide a &lt;strong&gt;salt&lt;/strong&gt; (exactly 32 bytes, base64url encoded representation of an ArrayBuffer). The authenticator evaluates this salt using its internal secret to generate the deterministically derived KEK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deriveKEK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Prepare your 32-byte salt&lt;/span&gt;
  &lt;span class="c1"&gt;// Note: For real applications, this salt can be deterministically &lt;/span&gt;
  &lt;span class="c1"&gt;// generated or stored alongside the encrypted data.&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;randomBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;prfSaltObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64Url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Add the PRF extension to the authentication request&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createAuthenticationOptionsFromJson&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s"&gt;"challenge"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"your-server-generated-challenge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"rpId"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"extensions"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"prf"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"eval"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prfSaltObject&lt;/span&gt; &lt;span class="c1"&gt;// Pass the salt to the PRF evaluator&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Authenticate the user&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Extract the derived KEK&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;prfResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clientExtensionResults&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;prf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prfResult&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;prfResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'first'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;derivedKEK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prfResult&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'first'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Successfully derived KEK: &lt;/span&gt;&lt;span class="si"&gt;$derivedKEK&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

     &lt;span class="c1"&gt;// 🚀 You can now use `derivedKEK` (base64url encoded) as a symmetric &lt;/span&gt;
     &lt;span class="c1"&gt;// key for AES encryption to decrypt your local offline data!&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'KEK Derivation is either not supported on this passkey or failed.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Best Practices and Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Salt Management&lt;/strong&gt;: The 32-byte salt you pass during authentication (&lt;code&gt;eval: {"first": salt}&lt;/code&gt;) is required every time you want to derive the same key. You can safely store this salt in plaintext alongside the encrypted file on the device, as the KEK can only be derived if the user successfully performs a biometric passkey authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why Derive at Authentication vs Registration?&lt;/strong&gt;: During registration, we only enable the feature (asking the authenticator if it supports PRF). The KEK is not actively stored anywhere; it is mathematically derived using your specific &lt;code&gt;salt&lt;/code&gt; and a secret tied to the passkey. Thus, providing the &lt;code&gt;salt&lt;/code&gt; and evaluating the PRF string must happen during authentication whenever the key is needed to unlock your vault. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback Mechanisms&lt;/strong&gt;: Not all operating systems or physical security keys support the PRF extension yet. Always check the result to ensure the key was derived, and consider implementing a fallback mechanism (like a traditional PIN/Password) for unsupported devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptography&lt;/strong&gt;: The derived key is returned as a base64url encoded string. You will need to decode it back into bytes before passing it to symmetric cryptographic algorithms (like AES-GCM) provided by libraries such as &lt;code&gt;cryptography&lt;/code&gt; or &lt;code&gt;pointycastle&lt;/code&gt; in Flutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Passkey PRF extension is a powerful tool for developers looking to build highly secure, privacy-first, offline-capable applications. By using &lt;code&gt;flutter_passkey_service&lt;/code&gt;, deriving a Key Encryption Key is as simple as flipping a boolean during registration and passing a salt during authentication. &lt;/p&gt;

&lt;p&gt;Encrypting user data without forcing them to remember complex passwords is now a reality. Happy coding!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>security</category>
      <category>passkey</category>
      <category>kek</category>
    </item>
    <item>
      <title>Stop Fighting Raw ZPL: Building a Modern Zebra Printer Pipeline in Flutter</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Sun, 22 Mar 2026 11:26:21 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/stop-fighting-raw-zpl-building-a-modern-zebra-printer-pipeline-in-flutter-5h0b</link>
      <guid>https://dev.to/tri_dev_dhm/stop-fighting-raw-zpl-building-a-modern-zebra-printer-pipeline-in-flutter-5h0b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffor9cegi8j9leslrl9kk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffor9cegi8j9leslrl9kk.png" alt=" " width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s be honest. When your product manager walks over and says, &lt;em&gt;“Hey, we need our app to print shipping labels directly to those industrial Zebra printers in the warehouse,”&lt;/em&gt; your stomach drops.&lt;/p&gt;

&lt;p&gt;If you’ve built mobile printing pipelines before, you know exactly why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hardware Chaos:&lt;/strong&gt; It involves wrestling with native iOS and Android SDKs to magically discover network devices spanning across both TCP/IP Wi-Fi and finicky Bluetooth antennas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Formatting" Language:&lt;/strong&gt; You are forced to write raw &lt;strong&gt;ZPL (Zebra Programming Language)&lt;/strong&gt; commands by hand. It’s an archaic string language where &lt;code&gt;^FO50,50^ADN,36,20^FDHello^FS&lt;/code&gt; is somehow considered a "readable" line of code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building this from scratch is painful, incredibly error-prone, and almost guarantees bugs in production. &lt;/p&gt;

&lt;p&gt;But what if you could interact with enterprise hardware the same way you build Flutter UI—declaratively and safely? In this article, I want to show you how to build a rock-solid, production-ready label printing pipeline using two synergized open-source packages: &lt;a href="https://github.com/minhtri1401/flutter_zpl_printer" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_zpl_printer&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pub.dev/packages/flutter_zpl_generator" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_zpl_generator&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ The 1-2 Punch: Separating Hardware from Design
&lt;/h2&gt;

&lt;p&gt;The secret to a maintainable printing pipeline is separating the &lt;strong&gt;hardware connection&lt;/strong&gt; from the &lt;strong&gt;label design&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;We achieve this by combining two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;flutter_zpl_printer&lt;/code&gt;:&lt;/strong&gt; A native Plugin acting as your hardware bridge. It safely wraps the official Zebra Link-OS SDK, handles thread-safe discovery, and shoots payloads to the printer without blocking the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;flutter_zpl_generator&lt;/code&gt;:&lt;/strong&gt; A pure-Dart UI library. Instead of fighting raw strings, it lets you lay out labels programmatically using a clean, typed Dart tree (just like Flutter Widgets!).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how you stitch them together into a seamless feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Taming the Hardware Connections
&lt;/h2&gt;

&lt;p&gt;Before we can draw a label, we need to locate a printer. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;(⚠️ **Crucial Gotcha&lt;/em&gt;&lt;em&gt;: Before scanning, ensure your app holds the correct runtime permissions! For Android, you absolutely need &lt;code&gt;BLUETOOTH_CONNECT&lt;/code&gt;, &lt;code&gt;BLUETOOTH_SCAN&lt;/code&gt;, and Location permissions. On iOS, you must configure &lt;code&gt;UISupportedExternalAccessoryProtocols&lt;/code&gt; for &lt;code&gt;com.zebra.rawport&lt;/code&gt; in your &lt;code&gt;Info.plist&lt;/code&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once permissions are cleared, finding a printer on the network (both Wi-Fi and Bluetooth) is surprisingly effortless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_zpl_printer/flutter_zpl_printer.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;printerPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterZplPrinter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Listen for printers popping up on the network&lt;/span&gt;
&lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onPrinterFound&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PrinterDevice&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Caught one! &lt;/span&gt;&lt;span class="si"&gt;${device.name}&lt;/span&gt;&lt;span class="s"&gt; via &lt;/span&gt;&lt;span class="si"&gt;${device.type.name}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;${device.address}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Start the scan&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startDiscovery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the returned &lt;code&gt;PrinterDevice&lt;/code&gt;, you acquire the exact IP or MAC Address needed to instantly open a native socket connection: &lt;code&gt;await printerPlugin.connect(device.address, device.type)&lt;/code&gt;. &lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Defending the User Experience
&lt;/h2&gt;

&lt;p&gt;Here is the number one mistake developers make with physical printers: &lt;strong&gt;They fire off the print payload blindly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the print head is open, or the printer is out of labels, a blind print job disappears into a black hole. Your Flutter user clicks "Print," nothing happens, and they click it 15 more times in frustration. &lt;/p&gt;

&lt;p&gt;Instead of dealing with phantom bugs, ask the hardware how it's feeling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Query the physical sensors on the Zebra printer&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isReadyToPrint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPaperOut&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;showUserAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Please load more labels!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isHeadOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;showUserAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'The print hatch is open!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isHeadTooHot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;showUserAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Printer is overheating, please wait.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Abort the print job&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By safely hooking into &lt;code&gt;getStatus()&lt;/code&gt;, you transform silent hardware failures into actionable UI events for the warehouse worker.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: Painting the Canvas (Without the Pain)
&lt;/h2&gt;

&lt;p&gt;Okay, your connection is open and the printer is ready. Now we need to tell it &lt;em&gt;what&lt;/em&gt; to print.&lt;/p&gt;

&lt;p&gt;If you aren't using &lt;code&gt;flutter_zpl_generator&lt;/code&gt;, you eventually end up writing code that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 💀 Nightmare Fuel:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;rawZpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"^XA^FO20,20^A0N,40,40^FDShipping Label^FS"&lt;/span&gt;
               &lt;span class="s"&gt;"^FO20,80^BY3^BCN,100,Y,N,N^FD12345678^FS^XZ"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try maintaining that when marketing asks you to "move the barcode slightly to the left."&lt;/p&gt;

&lt;p&gt;Instead, let's treat ZPL generation the same way we build Flutter Widgets. Using &lt;code&gt;ZplGenerator&lt;/code&gt;, you construct a strongly typed hierarchy. It handles all the coordinate math, formatting constraints, and syntax generation under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_zpl_generator/flutter_zpl_generator.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ZplGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;config:&lt;/span&gt; &lt;span class="n"&gt;ZplConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;printWidth:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 4 inches at 203 DPI&lt;/span&gt;
    &lt;span class="nl"&gt;labelLength:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 3 inches at 203 DPI&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;commands:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Clean, readable, and explicitly typed!&lt;/span&gt;
    &lt;span class="n"&gt;ZplText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;text:&lt;/span&gt; &lt;span class="s"&gt;'SHIPPING LABEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="n"&gt;ZplBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;772&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Draw a divider line&lt;/span&gt;

    &lt;span class="n"&gt;ZplBarcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;'PKG-12345678'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;type:&lt;/span&gt; &lt;span class="n"&gt;ZplBarcodeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Type-safe enum! Never memorize ^BC again.&lt;/span&gt;
      &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;x:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;y:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles your Dart UI tree directly into bullet-proof ZPL:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;safeZplPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Drop-Mic Moment
&lt;/h3&gt;

&lt;p&gt;Passing that generated, zero-syntax-error payload from the generator right into your waiting hardware connection takes exactly one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printZpl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safeZplPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Always remember to close your sockets!&lt;/span&gt;
&lt;span class="n"&gt;printerPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Wrapping Up: 3 Golden Rules for Printer Integrations
&lt;/h2&gt;

&lt;p&gt;If you're deploying this to production, keep these three rules of thumb in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DPI is Everything:&lt;/strong&gt; ZPL coordinates rely on dots, not pixels. A standard Zebra is 203 DPI (Dots Per Inch). If you want an element 2 inches wide, set your width to &lt;code&gt;406&lt;/code&gt;. If you aren't sure what your printer's physical DPI is, use &lt;code&gt;await printerPlugin.getSettings()&lt;/code&gt; and check the &lt;code&gt;head.resolution.in_dpi&lt;/code&gt; key!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview Before You Print:&lt;/strong&gt; Physical labels cost money, and testing your layout by printing 50 drafts wastes massive amounts of time. Drop your &lt;code&gt;generator.build()&lt;/code&gt; output into the &lt;a href="http://labelary.com/viewer.html" rel="noopener noreferrer"&gt;Labelary Online Viewer&lt;/a&gt; to instantly preview your UI right in your browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always Disconnect:&lt;/strong&gt; Background processes hold fast to hardware sockets. If your app crashes or moves to the background without calling &lt;code&gt;printerPlugin.disconnect()&lt;/code&gt;, the printer might lock up, forcing the user to physically reboot it. Wrap your connections in a &lt;code&gt;try / finally&lt;/code&gt; block.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building hardware integrations doesn't have to be a nightmare. By isolating your connection logic completely away from your layout logic, you create a robust, highly readable, and easily maintainable printing pipeline.&lt;/p&gt;

&lt;p&gt;To start building, check out the &lt;a href="https://github.com/minhtri1401/flutter_zpl_printer" rel="noopener noreferrer"&gt;Flutter Zpl Printer plugin here&lt;/a&gt; and grab the &lt;a href="https://pub.dev/packages/flutter_zpl_generator" rel="noopener noreferrer"&gt;Flutter Zpl Generator&lt;/a&gt; on Pub.dev!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>zpl</category>
      <category>zebra</category>
      <category>printer</category>
    </item>
    <item>
      <title>Integrate Passkey &amp; WebAuthn in Flutter: A Comprehensive Guide</title>
      <dc:creator>Trí Đặng</dc:creator>
      <pubDate>Wed, 10 Sep 2025 15:16:26 +0000</pubDate>
      <link>https://dev.to/tri_dev_dhm/unlock-the-future-of-authentication-a-guide-to-passwordless-login-with-passkey-516b</link>
      <guid>https://dev.to/tri_dev_dhm/unlock-the-future-of-authentication-a-guide-to-passwordless-login-with-passkey-516b</guid>
      <description>&lt;h2&gt;
  
  
  I. Introduction: The Passwordless Revolution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem with Passwords
&lt;/h3&gt;

&lt;p&gt;For decades, passwords have been the standard for digital security, but they come with a host of problems. They are frequently stolen in data breaches, forgotten by users, and are a primary target for phishing attacks. The constant need to create, remember, and manage complex passwords leads to user friction and insecure practices like password reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing Passkeys
&lt;/h3&gt;

&lt;p&gt;Passkeys represent a monumental shift in authentication technology. They are a password replacement that offers a faster, easier, and more secure sign-in experience. Backed by industry standards from the FIDO Alliance, passkeys are built on the &lt;strong&gt;WebAuthn&lt;/strong&gt; standard. WebAuthn (Web Authentication) is a web standard published by the World Wide Web Consortium (W3C) that enables passwordless authentication. It provides a standardized API for creating and accessing public-key credentials (a public and private key), which is what passkeys are. This makes them resistant to phishing and virtually impossible to guess. Think of WebAuthn as the technical specification and 'passkey' as the user-friendly term for the credentials it manages.&lt;/p&gt;

&lt;p&gt;The core benefits of passkeys include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Enhanced Security&lt;/strong&gt;: Since the private key never leaves the user's device, the risk of server-side breaches is significantly reduced.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;User Convenience&lt;/strong&gt;: Users can authenticate with a simple biometric verification (like Face ID or a fingerprint scan) or a device PIN.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cross-Device Sync&lt;/strong&gt;: Passkeys are synced across a user's devices using services like iCloud Keychain and Google Password Manager, allowing for seamless authentication on any of their devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why flutter_passkey_service?
&lt;/h3&gt;

&lt;p&gt;For Flutter developers looking to integrate this cutting-edge technology, &lt;code&gt;flutter_passkey_service&lt;/code&gt; is the perfect solution. This package provides a simple, developer-friendly API to bring passkey authentication to your Flutter apps. It abstracts away the complexity of native platform implementations, allowing you to add secure, passwordless login with minimal effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  II. Key Features of flutter_passkey_service
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Passwordless Authentication&lt;/strong&gt;: Leverage the full security and UX benefits of passkeys to provide a modern authentication experience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cross-Platform Support&lt;/strong&gt;: The package offers native implementations for &lt;strong&gt;iOS 16.0+&lt;/strong&gt; and &lt;strong&gt;Android API 28+&lt;/strong&gt;, ensuring your app can reach a wide audience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;WebAuthn Compliance&lt;/strong&gt;: Built on the FIDO2/WebAuthn standard, it provides enterprise-level security that your users can trust.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cross-Device Sync&lt;/strong&gt;: Works seamlessly with iCloud Keychain and Google Password Manager for easy passkey synchronization.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Simple, Type-Safe API&lt;/strong&gt;: Designed to be intuitive and easy to use, enabling quick and reliable integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  III. Getting Started: Installation and Setup
&lt;/h2&gt;

&lt;p&gt;Integrating &lt;code&gt;flutter_passkey_service&lt;/code&gt; into your project is straightforward.&lt;/p&gt;

&lt;p&gt;First, add the package to your &lt;code&gt;pubspec.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_passkey_service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.0.5&lt;/span&gt; &lt;span class="c1"&gt;# Check for the latest version on pub.dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run &lt;code&gt;flutter pub get&lt;/code&gt; in your terminal to install the package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IV. How to Use the Package: A Practical Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: The Basics
&lt;/h3&gt;

&lt;p&gt;The package revolves around two main operations: registration (creating a new passkey) and authentication (signing in with an existing passkey). Both operations require a &lt;code&gt;challenge&lt;/code&gt; from your backend server to ensure security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Registering a Passkey
&lt;/h3&gt;

&lt;p&gt;Registration is the process of creating a new passkey for a user. Your app will ask the platform (iOS or Android) to generate a new key pair. The private key is stored securely on the device, while the public key is sent to your server for storage.&lt;/p&gt;

&lt;p&gt;Here is an example of how to call the registration function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_passkeyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_registerPasskey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 1. Request a challenge from your server&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'your_server_generated_challenge_string'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'user_unique_id_from_your_db'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// 2. Call the register method&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_passkeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;rpName:&lt;/span&gt; &lt;span class="s"&gt;'Your App Name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'your.relying.party.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g., 'example.com'&lt;/span&gt;
        &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;username:&lt;/span&gt; &lt;span class="s"&gt;'user@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;displayName:&lt;/span&gt; &lt;span class="s"&gt;'User Full Name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 3. Send the response to your server to verify and store the public key&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Registration successful: &lt;/span&gt;&lt;span class="si"&gt;$response&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// await myApi.verifyRegistration(response);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PasskeyException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle exceptions specific to passkey operations&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Passkey registration error: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle other potential errors&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'An unexpected error occurred: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;challenge&lt;/code&gt;: A unique, randomly generated string from your server for this specific registration attempt.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;rpName&lt;/code&gt;: The human-readable name of your application (Relying Party).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;rpId&lt;/code&gt;: The Relying Party ID. This is typically your service's domain name. It's a security measure to scope the credential.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;userId&lt;/code&gt;: A unique, non-personally identifiable identifier for the user from your database.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;username&lt;/code&gt;: The username for the account, often an email.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;displayName&lt;/code&gt;: A user-friendly name for the account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Authenticating with a Passkey
&lt;/h3&gt;

&lt;p&gt;Once a user has a registered passkey, they can use it to sign in. The process is similar: your server provides a challenge, your app asks the platform to sign the challenge with the private key, and the signature is sent back to the server for verification.&lt;/p&gt;

&lt;p&gt;Here is an example of the authentication flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_passkeyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 1. Request a challenge from your server&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'your_server_generated_challenge_string'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// 2. Call the authenticate method&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_passkeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'your.relying.party.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g., 'example.com'&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 3. Send the response to your server for verification&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Authentication successful: &lt;/span&gt;&lt;span class="si"&gt;$response&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// await myApi.verifyAuthentication(response);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PasskeyException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle authentication failures&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Passkey authentication error: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Handle other potential errors&lt;/span&gt;
      &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'An unexpected error occurred: &lt;/span&gt;&lt;span class="si"&gt;$e&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Parameters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;challenge&lt;/code&gt;: A unique challenge from your server for this authentication attempt.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;rpId&lt;/code&gt;: The Relying Party ID, which must match the one used during registration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Complete Code Example
&lt;/h3&gt;

&lt;p&gt;Here’s a simple Flutter widget that demonstrates both flows with buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_passkey_service/flutter_passkey_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PasskeyDemoScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PasskeyDemoScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;passkeyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FlutterPasskeyService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;passkeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'server_generated_challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;rpName:&lt;/span&gt; &lt;span class="s"&gt;'Flutter Passkey Demo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'demo.passkey.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;userId:&lt;/span&gt; &lt;span class="s"&gt;'12345'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;username:&lt;/span&gt; &lt;span class="s"&gt;'testuser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;displayName:&lt;/span&gt; &lt;span class="s"&gt;'Test User'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Registration Success'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PasskeyException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Registration Failed: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;passkeyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;challenge:&lt;/span&gt; &lt;span class="s"&gt;'server_generated_challenge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;rpId:&lt;/span&gt; &lt;span class="s"&gt;'demo.passkey.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Authentication Success'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PasskeyException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ScaffoldMessenger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;showSnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;SnackBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Authentication Failed: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Passkey Demo'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Register a Passkey'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Authenticate with Passkey'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  V. Advanced Topics and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Handling Errors
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PasskeyException&lt;/code&gt; provides details about what went wrong (e.g., user cancellation, invalid challenge, credential not found). Catch this specific exception to provide clear, actionable feedback to the user. Avoid generic error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  UI/UX Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Educate Your Users&lt;/strong&gt;: Briefly explain what passkeys are and why they are beneficial.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clear Prompts&lt;/strong&gt;: Use clear and concise language for registration and sign-in buttons, like "Sign in with a passkey."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Graceful Fallbacks&lt;/strong&gt;: Always provide an alternative sign-in method for users who cannot or do not want to use passkeys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integrating with Your Backend
&lt;/h3&gt;

&lt;p&gt;A secure backend is crucial for a passkey implementation. Your server is responsible for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generating and issuing cryptographic challenges.&lt;/li&gt;
&lt;li&gt; Storing user information and public keys.&lt;/li&gt;
&lt;li&gt; Verifying the cryptographic signatures received from the app during registration and authentication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many server-side libraries available to help with WebAuthn compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  VI. Conclusion
&lt;/h2&gt;

&lt;p&gt;By integrating &lt;code&gt;flutter_passkey_service&lt;/code&gt;, you can offer your users a state-of-the-art authentication experience that is both highly secure and incredibly convenient. Moving beyond passwords is not just a trend; it's the future of digital identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Call to Action
&lt;/h3&gt;

&lt;p&gt;We encourage you to try out the package in your next Flutter project. Your contributions and feedback are welcome to help improve the package for everyone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Pub.dev&lt;/strong&gt;: &lt;a href="https://pub.dev/packages/flutter_passkey_service" rel="noopener noreferrer"&gt;https://pub.dev/packages/flutter_passkey_service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/minhtri1401/flutter_passkey_service" rel="noopener noreferrer"&gt;https://github.com/minhtri1401/flutter_passkey_service&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Keywords
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Flutter, Passkey, Passwordless Authentication, WebAuthn, FIDO2, Dart, Mobile Security, iOS Passkey, Android Passkey, flutter_passkey_service&lt;/em&gt;&lt;/p&gt;

</description>
      <category>passkey</category>
      <category>flutter</category>
      <category>dart</category>
      <category>passwordless</category>
    </item>
  </channel>
</rss>
