Authentication Process Using QR Codes in wacli: How It Works and Why It Differs from Sync
The wacli auth command initiates an interactive QR code authentication flow that creates a new WhatsApp Web session, while the wacli sync command operates only on existing authenticated sessions and explicitly disables QR code prompts.
steipete/wacli is a command-line interface for WhatsApp Web that distinguishes between initial authentication and subsequent synchronization. The repository implements a clear separation between the interactive QR code login flow and non-interactive sync operations through distinct command implementations and strict state validation.
How QR Code Authentication Works in wacli
The authentication flow spans three architectural layers, from the CLI surface down to the low-level WhatsApp client implementation.
The Three-Layer Architecture
| Layer | Responsibility | Key Source File |
|---|---|---|
| CLI command | Starts the process, configures QR options, renders the QR code in the terminal using qrterminal. | cmd/wacli/auth.go – the AllowQR: true flag and the OnQRCode callback |
| App wrapper | Opens the WhatsApp client and forwards QR options to the underlying connection. | internal/app/app.go – Connect(ctx, allowQR, qrWriter) forwards to wa.Connect |
| WhatsApp client | Connects to WhatsApp Web, obtains a QR channel when unauthenticated, streams events, and blocks until scan succeeds. | internal/wa/client.go – Connect implements the QR flow handling code, success, timeout, and error events |
Step-by-Step Authentication Flow
-
User invokes
wacli auth
The command builds aSyncOptionsstruct withAllowQR: trueand anOnQRCodecallback that prints the code viaqrterminal.GenerateHalfBlock. -
App layer forwards options
App.Synccallsa.Connect(ctx, true, qrWriter), which passes the boolean flag and writer to the low-level client. -
Client initializes QR channel
Insidewa.Connect, the code checksif !authed && connectOptions.AllowQR. When true, it callscli.GetQRChannel(ctx)to receive a channel of QR events. -
Event loop handles scan states
After connecting to WhatsApp Web, the client enters aselectloop reading from the QR channel:- "code": Invokes the
OnQRCodecallback to render the terminal QR code. - "success": Returns from the connect function, leaving the client authenticated.
- "timeout" or "error": Aborts with an error indicating authentication failure.
- "code": Invokes the
-
Bootstrap proceeds
Once authenticated,App.Synccontinues with the regular bootstrap process (downloading messages, contacts, and groups).
Key Differences Between auth and sync Commands
The sync command is explicitly designed as a post-authentication operation and enforces this constraint through code-level guards.
Interactive vs. Non-Interactive Operation
wacli auth: Interactive entry point. SetsAllowQR: trueand provides theOnQRCodecallback to enable visual QR code rendering in the terminal.wacli sync: Non-interactive, repeatable job. SetsAllowQR: falseand passesnilfor the QR writer, ensuring the process fails immediately if no session exists rather than prompting for a scan.
Authentication Requirements
In cmd/wacli/sync.go, the command first calls a.EnsureAuthed(), which fails fast if the store does not contain a valid session:
if err := a.EnsureAuthed(); err != nil {
return fmt.Errorf("not authenticated: %w", err)
}
The SyncOptions struct then explicitly disables QR:
res, err := a.Sync(ctx, appPkg.SyncOptions{
Mode: mode,
AllowQR: false, // QR disabled
DownloadMedia: downloadMedia,
// ...
})
If the client is unauthenticated when sync runs, wa.Connect returns an error instructing the user to run wacli auth first.
Implementation Details and Code Examples
QR Code Rendering in auth.go
The cmd/wacli/auth.go file defines the interactive authentication command:
res, err := a.Sync(ctx, appPkg.SyncOptions{
Mode: mode,
AllowQR: true, // Enable QR flow
DownloadMedia: downloadMedia,
RefreshContacts: true,
RefreshGroups: true,
IdleExit: idleExit,
OnQRCode: func(code string) { // Terminal QR renderer
fmt.Fprintln(os.Stderr, "\nScan this QR code with WhatsApp (Linked Devices):")
qrterminal.GenerateHalfBlock(code, qrterminal.M, os.Stderr)
fmt.Fprintln(os.Stderr)
},
})
Non-Interactive Sync in sync.go
The cmd/wacli/sync.go file disables QR and requires existing authentication:
// Ensure we have a stored session
if err := a.EnsureAuthed(); err != nil {
return fmt.Errorf("not authenticated: %w", err)
}
// Run sync without QR support
res, err := a.Sync(ctx, appPkg.SyncOptions{
Mode: mode,
AllowQR: false, // Explicitly disabled
DownloadMedia: downloadMedia,
RefreshContacts: refreshContacts,
RefreshGroups: refreshGroups,
IdleExit: idleExit,
MaxReconnect: maxReconnect,
})
Low-Level Client Logic in client.go
The internal/wa/client.go file implements the event-driven QR flow:
func (c *Client) Connect(ctx context.Context, allowQR bool, qrWriter func(string)) error {
// Check if already authenticated
authed := c.Store.HasSession()
var qrChan <-chan whatsmeow.QRChannelItem
if !authed && allowQR {
// Request QR channel only when unauthenticated and allowed
qrChan, _ = c.cli.GetQRChannel(ctx)
}
// Connect to WhatsApp Web
if err := c.cli.ConnectContext(ctx); err != nil {
return err
}
// If we need QR authentication, wait for scan
if qrChan != nil {
for evt := range qrChan {
switch evt.Event {
case "code":
// Render the QR code
qrWriter(evt.Code)
case "success":
// Authentication complete
return nil
case "timeout":
return fmt.Errorf("QR code expired")
case "error":
return fmt.Errorf("QR error: %v", evt.Error)
}
}
}
return nil
}
Summary
- Interactive Authentication: The
wacli authcommand enables QR code login by settingAllowQR: trueand providing anOnQRCodecallback that renders the code in the terminal usingqrterminal. - Three-Layer Flow: Authentication spans the CLI (
cmd/wacli/auth.go), the app wrapper (internal/app/app.go), and the WhatsApp client (internal/wa/client.go). - Event-Driven Protocol: The client uses
GetQRChannelto receive events (code,success,timeout,error) and blocks until the user scans the QR code with their mobile device. - Strict Separation: The
wacli synccommand disables QR (AllowQR: false) and callsEnsureAuthed()to enforce that authentication must precede synchronization, making it suitable for automated, non-interactive workflows.
Frequently Asked Questions
Can I use wacli sync without first running wacli auth?
No. The sync command explicitly checks for existing authentication via EnsureAuthed() and returns an error if no session exists in the store. You must run wacli auth first to establish the session via QR code.
What happens if I don't scan the QR code within the timeout period?
If the QR code expires, the internal/wa/client.go event loop receives a "timeout" event from the QR channel and returns an error, causing the wacli auth command to exit with a failure status. You must restart the command to generate a fresh QR code.
Where does wacli store authentication credentials after successful QR login?
The credentials are stored in the local database managed by the Store interface within the WhatsApp client. The client.go implementation checks c.Store.HasSession() to determine if authentication exists, and the sync.go command uses EnsureAuthed() to verify this state before proceeding.
Is the QR code authentication flow secure?
Yes, the implementation follows the official WhatsApp Web protocol. The QR code contains a temporary key exchange token that is only valid for a limited time (handled by the "timeout" event). The terminal rendering via qrterminal displays the code locally without transmitting it over networks, and the underlying whatsmeow library handles the cryptographic handshake once the mobile device scans the code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →