PatternAuthentication

1.1.0

Pattern Authentication is a Swift package that provides a customizable 3x3 grid-based pattern authentication system for iOS applications
Apex-Studios-LLC/PatternAuthentication

What's New

v1.1.0

2026-06-13T01:00:17Z

The security credential upgrade release keeps the original 1.x gesture UI APIs available for existing apps and adds the v1 credential-envelope flow for production use.

Added

  • Added the v1 GestureCredentialEnvelope API for storing gesture credentials as a structured envelope rather than a single legacy hash string.
  • Added GesturePattern to canonicalize selected grid indices before hashing, including a package-specific domain separator so the encoded gesture carries package and version context.
  • Added GestureHashConfiguration for KDF parameters, with .recommended using PBKDF2-HMAC-SHA256, 600,000 iterations, a 32-byte salt, and a 32-byte derived key.
  • Added GestureKDF so stored credentials can record the derivation algorithm used to create them.
  • Added GestureCredentialError for typed failures around salt generation, hashing, unsupported credential versions, malformed base64, malformed JSON, and invalid KDF metadata.
  • Added cryptographically secure random salt generation through Security framework APIs.
  • Added constant-time comparison for derived hash verification.
  • Added Codable, JSON, and base64 helpers for backend-friendly storage in document databases and APIs.
  • Added GridAuthenticator(.setCredential(...)) for new credential setup.
  • Added GridAuthenticator(.authenticateCredential(...)) for authenticating against a fetched v1 credential.
  • Added GridAuthenticator(.authenticateAndUpgrade(...)) for verifying a legacy v0 hash and returning a v1 credential envelope after a successful match.
  • Added Swift 6 language mode in Package.swift.
  • Added Sendable conformance for credential model types that can safely cross concurrency boundaries.
  • Added main-actor isolation for UI-owned state.
  • Added package tests for canonical gesture encoding, PBKDF2-HMAC-SHA256 known vectors, salt uniqueness, v1 credential verification, Codable round trips, base64 round trips, legacy verification, legacy-to-v1 migration, particle resource loading, and replay path behavior.
  • Added GitHub Actions CI for Swift 6 builds, tests, coverage extraction, and a 90% package-logic coverage gate.
  • Added Codecov configuration, LCOV conversion for Swift/Xcode coverage upload, and README badges for CI and coverage.
  • Added a DocC catalog for package documentation.
  • Added public API DocC comments for the non-view public types and methods.
  • Added Docs/MigrationGuide.md for legacy SHA-256 migration.
  • Added SECURITY.md with storage, lockout, session, and vulnerability-reporting guidance.

Changed

  • Reworked the README into a production integration guide with quickstart setup/authentication examples, backend responsibilities, Firestore-style storage guidance, security model notes, configuration defaults, troubleshooting, and versioning.
  • Updated the package security model from unsalted SHA-256-only storage to a salted credential envelope while preserving source compatibility for existing 1.x clients.
  • Updated setup and authentication examples to show app-owned backend fetch/save behavior.
  • Updated particle rendering so the particle canvas, drag hit testing, and circle geometry all use the same grid-local coordinate space.
  • Updated particle trails to interpolate fast drag movement and render at a fingertip-like thickness, reducing dotted gaps during quick swipes.
  • Preserved the original particle-only visual style rather than switching to a deterministic path overlay.
  • Updated SwiftUI layout so the grid itself defines the interactive drawing surface, avoiding full-screen coordinate drift in package consumers.
  • Updated debug behavior so raw gesture hashes are no longer printed.
  • Updated setup without confirmation so it completes after the first valid gesture while still honoring the default repeatInput: true replay.
  • Updated failed authentication behavior so the completion handler is called with false.
  • Updated the mutable static preference default to be Swift 6 concurrency friendly.

Deprecated

  • Deprecated hashArray(_:). Use GestureCredentialHasher.createCredential(for:configuration:) or the new GridAuthenticator(.setCredential(...)) flow instead.
  • Deprecated GridAuthenticator(.set(... completion: (String) -> Void)). Use GridAuthenticator(.setCredential(...)) for new setup flows.
  • Deprecated GridAuthenticator(.authenticate(expectedHash: ...)). Use GridAuthenticator(.authenticateCredential(...)) once a v1 envelope is stored.

Fixed

  • Fixed particle trails that could appear vertically offset from the selected circles in SwiftPM consumers.
  • Fixed particle trails that could look like a dotted line during fast finger movement.
  • Fixed replay cleanup so a previous path does not bleed into the next setup or authentication attempt.
  • Fixed SwiftPM resource loading coverage for the spark asset bundle.
  • Fixed failed authentication callbacks so integrators can reliably update UI after an incorrect pattern.

Security

  • New v1 credentials use a fresh per-credential salt and PBKDF2-HMAC-SHA256 rather than a raw unsalted gesture hash.
  • Verification derives a candidate hash with the stored credential parameters and compares it in constant time.
  • Credential metadata records KDF, iteration count, hash version, and derived key length so future migrations have enough context.
  • Legacy SHA-256 APIs remain only for compatibility and migration. New production integrations should use v1 credentials.
  • Apps remain responsible for backend storage, authorization, sessions, lockouts, attempt limits, audit logging, recovery, and deciding what a successful gesture unlocks.

Backwards Compatibility

  • This release adds APIs in 1.x.
  • Existing calls to GridAuthenticator(.set(... completion: (String) -> Void)), GridAuthenticator(.authenticate(expectedHash: ...)), and hashArray(_:) should continue to compile with deprecation warnings.
  • Existing stored legacy hashes can still be verified.
  • Existing production apps can migrate one user at a time with authenticateAndUpgrade after a successful legacy match.
  • repeatInput remains true by default.
  • Apps that exhaustively switch over package enums may need to recompile and handle newer cases.
  • Apps that depended on raw debug hash output should remove that dependency.

Migration Notes

  • For new users, store GestureCredentialEnvelope.Base64Representation fields in your app-owned backend or local secure storage.
  • For existing users, keep the legacy hash until authenticateAndUpgrade succeeds and your app has saved the returned v1 credential.
  • Do not delete a legacy hash if saving the new envelope fails.
  • Store all v1 fields together: salt, hash, kdf, iterations, hashVersion, and derivedKeyLength.
  • Treat the credential record as sensitive authenticator material.

Testing

  • Verified Swift 6 package builds for iOS.
  • Added a focused package test suite covering credential creation, verification, migration, encoding, resources, and particle behavior.
  • Added CI coverage enforcement at 90% or higher for package logic.
  • Verified the updated package through the sibling PatternAuthTest app on a physical iPhone.

Known Non-Goals

  • Argon2id remains a candidate for memory-hard offline attack resistance. v1 uses PBKDF2-HMAC-SHA256 to keep the package dependency-free and SwiftPM-friendly.

Swift versions Platforms CI codecov

Pattern Authentication

Pattern Authentication gives iOS apps a polished 3x3 gesture-pattern setup and authentication flow. Multi-user apps can use it for profile switching, household access, or secondary checks without asking each user for a password on each handoff.

The package owns the client-side gesture UI, canonical pattern encoding, salted credential creation, credential verification, legacy migration helpers, and particle-path feedback.

Your app owns the backend, account model, lockout policy, session creation, and authorization checks. Fetch stored credentials, save new credentials, enforce attempt limits, and grant access after a successful gesture match in your app or service.

Demo video

Requirements

  • iOS 15.0+
  • Swift 6.0+
  • Xcode 16+ or a Swift 6-compatible toolchain

Install

Add the package in Xcode:

https://github.com/Apex-Studios-LLC/PatternAuthentication.git

Or add it to a Package.swift manifest:

.package(
    url: "https://github.com/Apex-Studios-LLC/PatternAuthentication.git",
    from: "1.1.0"
)

Then add PatternAuthentication to your app target.

.target(
    name: "YourApp",
    dependencies: ["PatternAuthentication"]
)

Quick Start

Import the package anywhere you present the gesture UI.

import PatternAuthentication
import SwiftUI

Create a new v1 credential during setup:

struct GestureSetupView: View {
    let userID: String
    let saveCredential: (String, GestureCredentialEnvelope) async throws -> Void

    @State private var status = ""

    var body: some View {
        GridAuthenticator(.setCredential(
            minimumVertices: 6,
            color: .green,
            interactionMode: .drag,
            requireConfirmation: true,
            repeatInput: true,
            debug: false
        ) { credential in
            Task {
                do {
                    try await saveCredential(userID, credential)
                    status = "Pattern saved"
                } catch {
                    status = "Could not save pattern"
                }
            }
        })
    }
}

Authenticate against a credential your app fetched from storage:

struct GestureLoginView: View {
    let credential: GestureCredentialEnvelope
    let onAuthenticated: () -> Void

    @State private var failed = false

    var body: some View {
        GridAuthenticator(.authenticateCredential(
            credential: credential,
            color: .blue,
            interactionMode: .drag,
            debug: false
        ) { success in
            if success {
                onAuthenticated()
            } else {
                failed = true
            }
        })
    }
}

Credential Flow

The v1 API returns a GestureCredentialEnvelope:

public struct GestureCredentialEnvelope: Codable, Hashable, Sendable {
    public let salt: Data
    public let hash: Data
    public let kdf: GestureKDF
    public let iterations: Int
    public let hashVersion: Int
    public let derivedKeyLength: Int
}

For JSON or document databases, use the base64 representation:

let representation = credential.base64Representation

let payload: [String: Any] = [
    "salt": representation.salt,
    "hash": representation.hash,
    "kdf": representation.kdf.rawValue,
    "iterations": representation.iterations,
    "hashVersion": representation.hashVersion,
    "derivedKeyLength": representation.derivedKeyLength
]

To rebuild the envelope after fetching it:

let representation = GestureCredentialEnvelope.Base64Representation(
    salt: storedSalt,
    hash: storedHash,
    kdf: .pbkdf2SHA256,
    iterations: storedIterations,
    hashVersion: storedHashVersion,
    derivedKeyLength: storedDerivedKeyLength
)

let credential = try GestureCredentialEnvelope(
    base64Representation: representation
)

You can also encode and decode JSON directly:

let data = try credential.jsonData()
let decoded = try GestureCredentialEnvelope.fromJSONData(data)

Backend Responsibilities

Store the envelope fields with the user or profile record your app controls. A Firestore-style document might look like this:

{
  "gestureCredential": {
    "salt": "base64 salt",
    "hash": "base64 hash",
    "kdf": "pbkdf2-sha256",
    "iterations": 600000,
    "hashVersion": 1,
    "derivedKeyLength": 32,
    "updatedAt": "server timestamp"
  }
}

Your app or backend should also own:

  • Fetching the envelope before presenting GridAuthenticator(.authenticateCredential(...)).
  • Saving the envelope returned by GridAuthenticator(.setCredential(...)).
  • Enforcing lockouts, rate limits, re-authentication windows, and audit logging.
  • Deciding what a successful gesture unlocks.
  • Protecting reads and writes with your normal account authorization rules.
  • Migrating legacy hashes only after a successful legacy gesture match.

Attackers can read a salt without gaining the gesture. Treat the full credential record as sensitive authenticator material: restrict reads, use TLS, avoid logging it, and expose it only to clients authorized to authenticate that account.

Legacy Migration

The original package API returned a single unsalted SHA-256 string. The 1.x line keeps those APIs source-compatible and marks them deprecated:

GridAuthenticator(.set { legacyHash in
    // Deprecated: store only while migrating old installations.
})

GridAuthenticator(.authenticate(expectedHash: legacyHash) { success in
    // Deprecated: prefer authenticateCredential(credential:).
})

For production apps that already have legacy hashes, use authenticateAndUpgrade. It verifies the old hash first, then gives your app a v1 envelope to save.

GridAuthenticator(.authenticateAndUpgrade(
    expectedHash: legacyHash,
    configuration: .recommended,
    onCredentialUpgrade: { credential in
        Task {
            try await saveCredential(userID, credential)
            try await deleteLegacyHash(userID)
        }
    },
    completion: { success in
        if success {
            openUserWorkspace()
        }
    }
))

This upgrade callback only fires after a successful legacy match. If saving the new credential fails, keep the legacy hash and retry migration on a later successful login.

Security Model

The v1 credential format uses:

  • Canonical gesture encoding with a package-specific domain separator.
  • A fresh cryptographically secure random salt per credential.
  • PBKDF2-HMAC-SHA256 with 600,000 iterations by default.
  • 32-byte salts and 32-byte derived hashes by default.
  • Constant-time comparison for hash verification.
  • Codable, base64, and JSON helpers for app-controlled storage.

Gesture patterns have limited entropy compared with passwords or passkeys. Use this package for local app re-authentication, profile switching, child or household user gates, or lightweight secondary checks. Do not use a pattern gesture as your only account security boundary for high-value accounts. Pair it with your app's normal signed-in session, backend authorization, attempt limits, and recovery flow.

Argon2id would add memory-hard offline attack resistance. v1 uses PBKDF2-HMAC-SHA256 because Apple platforms provide it without a native dependency, which keeps SwiftPM integration simple.

Configuration

setCredential uses these defaults:

Parameter Default Notes
minimumVertices 6 Rejects short setup gestures.
color .blue Drives circles, glow, and particles.
interactionMode .drag Drag input is the supported production path.
requireConfirmation true Requires the user to repeat a setup pattern.
repeatInput true Replays the entered pattern by default.
debug false Shows safe diagnostics without printing raw hashes.
configuration .recommended PBKDF2-HMAC-SHA256, 600,000 iterations.

InteractionMode.tap remains for source compatibility and future UI work. Use .drag for production flows.

Troubleshooting

If the particle trail appears offset, check whether a wrapper view overrides the "GridSpace" coordinate space. The package draws particles from grid-local geometry captured through SwiftUI preferences.

If the particle trail appears dotted after direct customization, keep ParticleSystem.emissionSpacing lower than ParticleSystem.particleDiameter. The defaults interpolate fast drag movement and draw fingertip-sized particles so normal swipes read as a continuous trail.

If authentication always fails, confirm that you are passing the exact fetched GestureCredentialEnvelope back into .authenticateCredential. Salt, hash, KDF, iteration count, hash version, and derived key length must all match the stored record.

If migration does not save a v1 credential, check your onCredentialUpgrade callback. The package creates the envelope after a successful legacy match; your app writes it to storage.

If CI coverage fails, run the same command locally:

xcodebuild test \
  -scheme PatternAuthentication \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -enableCodeCoverage YES \
  -resultBundlePath TestResults.xcresult \
  SWIFT_VERSION=6 \
  SWIFT_STRICT_CONCURRENCY=complete

xcrun xccov view --report --json TestResults.xcresult > coverage.json
python3 Scripts/check_coverage.py coverage.json \
  --threshold 90 \
  --ignore Sources/PatternAuthentication/PatternAuthentication.swift

Versioning

The security credential upgrade adds APIs in 1.x. Deprecated legacy APIs stay available so existing apps can migrate without a breaking release. A future 2.0 release may remove legacy SHA-256 setup/authentication APIs and revisit reserved interaction modes.

See CHANGELOG.md for the full 1.1.0 release notes. See Docs/MigrationGuide.md and SECURITY.md for migration and operating guidance.

License

Pattern Authentication uses the MIT license. See LICENSE.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

  • None
Last updated: Wed Jul 22 2026 09:32:36 GMT-0900 (Hawaii-Aleutian Daylight Time)