PyObjC LocalAuthentication Framework
Wrappers for the framework LocalAuthentication on macOS. This library is part of the larger PyObjC project, a bridge between Python and Objective-C. It allows Python applications to leverage macOS's biometric authentication (Face ID, Touch ID) and passcode features for secure user authentication. Currently at version 12.1, it maintains an active release cadence, typically aligning with macOS SDK updates and Python version support.
Warnings
- breaking PyObjC frequently drops support for older Python versions. PyObjC 12.0 dropped support for Python 3.9, and PyObjC 11.0 dropped Python 3.8.
- breaking PyObjC 11.1 changed how Objective-C 'init' family methods are modeled, now correctly reflecting that they 'steal' a reference to `self` and return a new one, as per clang's Automatic Reference Counting (ARC) documentation.
- gotcha Unlike Objective-C, where sending a message to `nil` (equivalent to Python `None`) is a no-op, attempting to call a method on a Python `None` object (which PyObjC translates from `nil`) will raise an `AttributeError`.
- gotcha PyObjC is a macOS-specific library and will not install or run on other operating systems. The frameworks it wraps (like LocalAuthentication) are Apple-proprietary.
- gotcha PyObjC 10.3 introduced breaking changes around the interaction of `__init__` and `__new__` for Python subclasses of Objective-C objects. While 10.3.1 partially re-introduced `__init__` support when a user implements `__new__`, code relying on PyObjC's provided `__new__` still cannot use `__init__`. This can lead to `__init__` not being called or unexpected behavior.
Install
-
pip install pyobjc-framework-localauthentication
Imports
- LAContext
from LocalAuthentication import LAContext
- LAPolicy
from LocalAuthentication import LAPolicy
- LAError
from LocalAuthentication import LAError
Quickstart
import objc
from LocalAuthentication import LAContext, LAPolicy
def authenticate_user(reason="Authenticate to access sensitive data."):
context = LAContext.alloc().init()
error = objc.nil
can_evaluate = context.canEvaluatePolicy_error_(LAPolicy.deviceOwnerAuthenticationWithBiometrics, objc.byref(error))
if not can_evaluate:
if error is not objc.nil:
print(f"Biometric authentication not available: {error.localizedDescription()}")
else:
print("Biometric authentication not available.")
# Fallback to device passcode if biometrics not available
can_evaluate = context.canEvaluatePolicy_error_(LAPolicy.deviceOwnerAuthentication, objc.byref(error))
if not can_evaluate:
if error is not objc.nil:
print(f"Device passcode authentication not available: {error.localizedDescription()}")
else:
print("Device passcode authentication not available.")
return False
def reply_handler(success, err):
if success:
print("Authentication successful!")
elif err is not objc.nil:
print(f"Authentication failed: {err.localizedDescription()}")
else:
print("Authentication failed (unknown error).")
context.evaluatePolicy_localizedReason_reply_(LAPolicy.deviceOwnerAuthentication, reason, reply_handler)
# Example usage:
# Note: This will block the Python interpreter until authentication completes or fails.
# In a real GUI app, this would typically be run on a background thread or using a runloop.
authenticate_user()
# To run PyObjC event loop in a console application (optional, for persistent UI/callbacks):
# from PyObjCTools import AppHelper
# AppHelper.runEventLoop() # This should be called if you need to keep a UI or callbacks alive.