Introduction
KlugAuth lets users of an AIR app create an account or sign in with Sign in with Apple or Sign in with Google, using the sign-in sheet of the operating system. Your app receives the provider's signed identity token, which your server verifies before it creates or opens the account. No password is typed, stored or sent.
| Platform | Apple · Google |
| iOS 13+ | Apple: native sheet (Face ID / Touch ID) · Google: system sign-in window |
| iPad app on a Mac | Apple · Google, as on iOS |
| macOS 10.15+ | Apple: native sheet · Google: system sign-in window |
| Android | Apple: not available · Google: Android account sheet (Credential Manager) |
| Windows, others | Not available; isSupported and isGoogleSupported are false |
Google sign-in on iOS and macOS uses the standard OAuth 2.0 authorization-code flow with PKCE in the system's web authentication sheet; no Google SDK is bundled.
| Extension ID | com.klug.auth.KlugAuth |
| Version | 1.2.0 |
| Architectures | iOS arm64 · macOS arm64 and x86_64 · Android armv7, arm64, x86, x86_64 |
| Delivery | com.klug.auth.KlugAuth.ane (the extension), the ActionScript library (SWC) for code completion and compiling, and this documentation. |
| Package | com.klug.auth: KlugAuth, KlugAuthEvent |
Requirements
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 13 or later |
| macOS | macOS 10.15 or later |
| Android | Android 6.0 (API 23) or later with Google Play services and a Google account on the device |
| Apple | An Apple Developer account; the Sign in with Apple capability on every App ID that uses it |
| A Google Cloud project with OAuth clients (web, Android, iOS) | |
| Server | Your own backend that verifies the identity tokens |
| Permissions | None for the sign-in itself. Your app needs network access to send the token to your server. |
Add the extension
1 · Copy the ANE and declare it
Copy com.klug.auth.KlugAuth.ane into the extension folder of your project, add it to your build (IDE: native extension of the project; command line: -external-library-path for compiling, -extdir for packaging) and declare it:
<extensions>
<extensionID>com.klug.auth.KlugAuth</extensionID>
</extensions>
2 · iOS (and the iPad app on the Mac)
Sign in with Apple needs one entitlement. Google needs no Info.plist entry and no URL scheme: the sign-in window returns to the app by itself.
<iPhone>
<Entitlements><![CDATA[
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
]]></Entitlements>
</iPhone>
Package with a provisioning profile that was created after Sign in with Apple was enabled on the App ID (see Guides · Apple setup), otherwise signing fails or the sheet reports an error.
3 · macOS
For the macOS build, add the entitlements to the entitlements file you sign the app with (codesign --entitlements). Sign in with Apple needs the same entitlement as on iOS. Sandboxed apps (Mac App Store) also need outgoing network access, which the Google sign-in uses to complete the login:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
Embed a provisioning profile for the Mac App ID that includes Sign in with Apple. Restricted entitlements such as com.apple.developer.applesignin only work with such a profile.
4 · Android
No permission, activity or service has to be added to <manifestAdditions>. The Android libraries the extension uses (AndroidX Credential Manager and Google Identity) are declared inside the ANE and resolved automatically by the AIR SDK's Gradle build when you package the app; the build machine needs access to Google's Maven repository and Maven Central for that. Your app's usual network permission is enough to send the token to your server:
<android>
<manifestAdditions><![CDATA[
<manifest>
<!-- for your own request to your server, not for the sign-in sheet -->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
]]></manifestAdditions>
</android>
The Google console needs an Android OAuth client for your package name and signing certificate; see Guides · Google setup.
Getting started
KlugAuth is a singleton. Check what the device offers, add the listeners, create a fresh nonce and open the sheet. The call returns at once; the result arrives as a KlugAuthEvent. Send the token and the same raw nonce to your server.
import com.klug.auth.KlugAuth; import com.klug.auth.KlugAuthEvent; var auth:KlugAuth = KlugAuth.getInstance(); var nonce:String; appleButton.visible = auth.isSupported; // Sign in with Apple available here? auth.addEventListener( KlugAuthEvent.APPLE_SUCCESS, onAppleSuccess ); auth.addEventListener( KlugAuthEvent.APPLE_CANCELED, onCanceled ); auth.addEventListener( KlugAuthEvent.APPLE_ERROR, onError ); function onAppleButton():void { nonce = KlugAuth.generateNonce(); // new for every attempt if ( !auth.signInWithApple( nonce ) ) showMessage( "Sign in with Apple could not start." ); // your app function } function onAppleSuccess( e:KlugAuthEvent ):void { // never trust this on the device: let your server verify the token postToMyServer( "apple", e.identityToken, nonce, e.fullName ); // your app function } function onCanceled( e:KlugAuthEvent ):void { /* user closed the sheet */ } function onError( e:KlugAuthEvent ):void { trace( e.errorCode, e.errorMessage ); }
Guides
Apple setup (Apple Developer account)
- 1. In Certificates, Identifiers & Profiles → Identifiers, open the App ID of your iOS app (com.example.yourapp) and enable Sign In with Apple (as a primary App ID). Do the same for a separate macOS App ID if you ship a native Mac build (com.example.yourapp.desktop). The iPad app on the Mac uses the iOS App ID.
- 2. Create the provisioning profiles (development and distribution) again and use the new ones for packaging. Old profiles do not contain the capability.
- 3. Add the entitlement (iOS: app descriptor; macOS: signing entitlements), see Add the extension.
- 4. For the server: create a Sign in with Apple key under Keys if your server will exchange the authorization code or revoke tokens (needed for account deletion). Note your Team ID and the key ID; the private key stays on your server.
- 5. Design the button according to Apple's Human Interface Guidelines for Sign in with Apple (black or white, Apple logo, approved wording).
If your iOS app offers Sign in with Google or another third-party login, Apple's App Review Guidelines require an equivalent privacy-friendly option, which Sign in with Apple fulfils. Apps that let users create an account must also let them delete it; with Sign in with Apple, the server should then revoke the token (see Server-side verification below).
Google setup (Google Cloud console)
Create one project for your app and, under APIs & Services, configure the OAuth consent screen (app name, support e-mail, scopes openid, email, profile). Then create these OAuth client IDs under Credentials:
| Client type | Used for |
| Web application | Its client ID is what your Android app passes to signInWithGoogle(), and it is the token audience your server accepts for Android sign-ins. No redirect URI is needed. |
| Android | Package name of your Android app and the SHA-1 fingerprint of the certificate that signs it. This client is not used in code, but without it the Android sheet does not return a token. AIR prefixes the package name with air. unless you build without that prefix; enter the exact package name of the APK / AAB (air.com.example.yourapp). |
| iOS | Bundle ID of your iOS app. Its client ID is what your iOS and macOS builds pass to signInWithGoogle(), and the token audience your server accepts for those sign-ins. |
SHA-1 fingerprints. Read the fingerprint of your release certificate with keytool -list -v -keystore your-release.p12 -storetype pkcs12. If Google Play signs your app (Play App Signing), add a second Android client with the SHA-1 of the app signing key shown in the Play Console under App integrity; the upload certificate alone is not enough for store installs. Add a client for your debug certificate too if you test debug builds.
While the publishing status of the consent screen is Testing, only the listed test users can sign in. Publish it before the release.
Sign in with Apple
signInWithApple(rawNonce) opens Apple's sheet. It asks for the user's name and e-mail; the user can hide the e-mail behind an Apple relay address.
- Nonce: pass a fresh value from generateNonce(). Apple places its SHA-256 (lower-case hex) in the token's nonce claim; your server hashes the raw value it receives and compares.
- First authorization only: email, givenName and familyName are filled only the first time a user authorizes your app. Send them to your server right away. Later sign-ins return empty strings; the e-mail is still inside the identity token.
- userID is Apple's stable user identifier for your team, the same on all devices of that Apple ID (and equal to the token's sub). Store it to check the credential state later.
- authorizationCode is a short-lived, single-use code. Your server can exchange it with Apple for a refresh token, which it needs to revoke the link when the account is deleted.
- Only one Apple sheet can be open at a time: a second call while one is open returns false.
import com.klug.auth.KlugAuth; import com.klug.auth.KlugAuthEvent; var auth:KlugAuth = KlugAuth.getInstance(); var appleNonce:String; auth.addEventListener( KlugAuthEvent.APPLE_SUCCESS, onAppleSuccess ); auth.addEventListener( KlugAuthEvent.APPLE_CANCELED, onAppleCanceled ); auth.addEventListener( KlugAuthEvent.APPLE_ERROR, onAppleError ); function signInWithApple():void { if ( !auth.isSupported ) return; // Android, Windows appleNonce = KlugAuth.generateNonce(); if ( !auth.signInWithApple( appleNonce ) ) showMessage( "Please wait, sign-in is already open." ); // your app function } function onAppleSuccess( e:KlugAuthEvent ):void { saveAppleUserID( e.userID ); // your app function postToMyServer( { provider: "apple", id_token: e.identityToken, nonce: appleNonce, // the RAW value code: e.authorizationCode, // for refresh / revoke on the server name: e.fullName, // "" after the first authorization email: e.email // hint only: the server reads the token } ); } function onAppleCanceled( e:KlugAuthEvent ):void { // the user closed the sheet: stay on the screen, no message } function onAppleError( e:KlugAuthEvent ):void { log( "Apple sign-in failed " + e.errorCode + ": " + e.errorMessage ); // your app function showMessage( "Sign in with Apple is not available right now." ); }
Sign in with Google
signInWithGoogle(clientId, rawNonce) opens Google sign-in. The client ID depends on the platform:
| Android | The web client ID. The Android account sheet (Credential Manager) shows the Google accounts on the device. |
| iOS, iPad on Mac, macOS | The iOS client ID. A system sign-in window opens Google's sign-in page; an existing Google login in Safari is reused, and the user can pick the account. |
- Nonce: Google puts the raw value unchanged into the token's nonce claim.
- Every time: email, givenName, familyName are filled on every sign-in. userID is the account e-mail; for a stable key on your server use the token's sub claim.
- authorizationCode is always empty for Google.
- Returns false when the client ID is empty, a Google sign-in is already running, or (iOS / macOS) the client ID does not end in .apps.googleusercontent.com.
import com.klug.auth.KlugAuth; import com.klug.auth.KlugAuthEvent; import flash.system.Capabilities; const GOOGLE_WEB_CLIENT_ID:String = "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"; const GOOGLE_IOS_CLIENT_ID:String = "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com"; var auth:KlugAuth = KlugAuth.getInstance(); var googleNonce:String; googleButton.visible = auth.isGoogleSupported; // Android, iOS 13+, macOS 10.15+ auth.addEventListener( KlugAuthEvent.GOOGLE_SUCCESS, onGoogleSuccess ); auth.addEventListener( KlugAuthEvent.GOOGLE_CANCELED, onGoogleCanceled ); auth.addEventListener( KlugAuthEvent.GOOGLE_ERROR, onGoogleError ); function googleClientId():String { return Capabilities.manufacturer.indexOf( "Android" ) >= 0 ? GOOGLE_WEB_CLIENT_ID : GOOGLE_IOS_CLIENT_ID; } function signInWithGoogle():void { googleNonce = KlugAuth.generateNonce(); if ( !auth.signInWithGoogle( googleClientId(), googleNonce ) ) showMessage( "Google sign-in could not start." ); // your app function } function onGoogleSuccess( e:KlugAuthEvent ):void { postToMyServer( { provider: "google", id_token: e.identityToken, nonce: googleNonce, name: e.fullName } ); } function onGoogleCanceled( e:KlugAuthEvent ):void { } function onGoogleError( e:KlugAuthEvent ):void { if ( e.errorCode == "NO_ACCOUNT" ) // Android: no Google account on the device showMessage( "Add a Google account in the device settings first." ); else showMessage( "Google sign-in failed. Please try again." ); log( "Google " + e.errorCode + ": " + e.errorMessage ); }
Send the token to your server
The app's only job after a successful sign-in is to pass the token and the raw nonce to your backend over HTTPS. The server verifies everything and answers with its own session; the app never decides on the basis of email or userID from the event alone.
import flash.events.Event; import flash.events.IOErrorEvent; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestMethod; import flash.net.URLVariables; function postToMyServer( fields:Object ):void { var vars:URLVariables = new URLVariables(); for ( var key:String in fields ) vars[ key ] = fields[ key ]; var req:URLRequest = new URLRequest( "https://api.example.com/auth/social" ); req.method = URLRequestMethod.POST; req.data = vars; var loader:URLLoader = new URLLoader(); loader.addEventListener( Event.COMPLETE, function( e:Event ):void { var answer:Object = JSON.parse( String( loader.data ) ); if ( answer.ok ) startSession( answer.session ); // your app function else showMessage( answer.message ); }); loader.addEventListener( IOErrorEvent.IO_ERROR, function( e:IOErrorEvent ):void { showMessage( "No connection to the server." ); }); loader.load( req ); }
Server-side verification
The identity token is a signed JSON Web Token (JWT). Treat everything that comes from the app as untrusted until the token is verified on your server. Use a maintained JWT library for your server language; do not decode and trust the payload without checking the signature.
| Check | Apple · Google |
| Signature | Verify against the provider's published public keys (JWKS), selected by the token's kid. Apple: https://appleid.apple.com/auth/keys · Google: https://www.googleapis.com/oauth2/v3/certs. Cache the keys and refresh them when an unknown kid appears. |
| iss | Apple: https://appleid.apple.com · Google: https://accounts.google.com or accounts.google.com |
| aud | Apple: the bundle ID of your app (iOS bundle ID, or the Mac bundle ID for a native Mac build) · Google: one of your client IDs (web client ID for Android, iOS client ID for iOS and macOS). Reject any other audience. |
| exp / iat | Not expired, issued recently. Allow only a small clock skew. |
| nonce | Apple: equals SHA-256 (lower-case hex) of the raw nonce the app sent · Google: equals the raw nonce. Accept each nonce only once. |
| Identity | Use sub as the account key for the provider. Use email from the token, not from the request; for Google, check email_verified. |
- Never trust the client: e-mail, name and user ID sent next to the token are hints for display only (for example a proposed user name).
- Replay: keep used nonces (or token IDs) for the lifetime of a token and reject repeats.
- Apple authorization code: exchange it at Apple's token endpoint with a client secret (a JWT your server signs with the Sign in with Apple key) and store the refresh token. When the user deletes the account, call Apple's revoke endpoint with it.
- Linking: if an account with the same verified e-mail already exists, decide on your server whether to link it, and ask the user when in doubt.
- Answer the app with your own session token; do not keep using the provider's identity token as a session.
Check a returning Apple user
Users can stop using Sign in with Apple for your app in their Apple ID settings. At app start, ask Apple about the stored user ID with getCredentialState(userID); the answer arrives as APPLE_CREDENTIAL_STATE. iOS and macOS only; on Android the call returns false.
| authorized | Still connected; keep the user signed in. |
| revoked | The user disconnected your app; sign out. |
| notFound | No such user for this Apple ID on this device; sign out or show sign-in. |
| transferred | The app was transferred to another developer team; the user has to be migrated on your server. |
| unknown | Apple could not answer; keep the current state and ask again later. |
auth.addEventListener( KlugAuthEvent.APPLE_CREDENTIAL_STATE, function( e:KlugAuthEvent ):void { if ( e.state == "revoked" || e.state == "notFound" ) signOut(); // your app function }); var savedID:String = loadAppleUserID(); // your app function if ( auth.isSupported && savedID != "" ) auth.getCredentialState( savedID );
Show only what the device supports
Decide the visible buttons once, when the sign-in screen opens:
var auth:KlugAuth = KlugAuth.getInstance(); appleButton.visible = auth.isSupported; // iOS 13+, iPad on Mac, macOS 10.15+ googleButton.visible = auth.isGoogleSupported; // Android, iOS 13+, macOS 10.15+ if ( !appleButton.visible && !googleButton.visible ) showPasswordLogin(); // your app function, e.g. on Windows
Background, return and errors
While a sheet is open, the system shows its own UI above your app. On Android and in the Google window on iOS / macOS, your app may receive Event.DEACTIVATE and Event.ACTIVATE around it; do not treat this as the user leaving the sign-in screen. The result event arrives when the sheet closes. Keep the nonce in a variable until then.
- Every attempt ends with exactly one success, cancel or error event of its provider.
- Cancel is never reported as an error. Show no message for it.
- A false return value means no sheet was opened and no event will follow.
- Methods never throw; on platforms without support they return false.
API reference
class KlugAuth
Package com.klug.auth · extends flash.events.EventDispatcher · singleton.
| Member | Description |
| getInstance():KlugAuth | Static. Returns the single instance and creates it on first use. |
| KlugAuth() | Constructor. Do not call it directly: a second call throws an Error. Use getInstance(). |
| generateNonce():String | Static. 32 cryptographically random bytes as a 64-character lower-case hex string. Use a new one for every sign-in attempt. |
| isSupported:Boolean | Read-only. True where Sign in with Apple is available: iOS 13+ (including the iPad app on a Mac) and macOS 10.15+. False on Android and other platforms. |
| isGoogleSupported:Boolean | Read-only. True where Sign in with Google is available: Android, iOS 13+, macOS 10.15+. False on other platforms. |
| signInWithApple(rawNonce:String = ""):Boolean | Opens Apple's sign-in sheet, requesting name and e-mail. rawNonce: the raw nonce; its SHA-256 is sent to Apple (empty: no nonce). Returns true if the sheet was started; false if not supported or an Apple sign-in is already running. Result: APPLE_SUCCESS, APPLE_CANCELED or APPLE_ERROR. iOS, macOS. |
| signInWithGoogle(clientId:String, rawNonce:String = ""):Boolean | Opens Google sign-in. clientId: Android: the web client ID; iOS / macOS: the iOS client ID. rawNonce: placed unchanged in the token (empty: no nonce). Returns true if started; false if the client ID is empty or invalid, not supported, or a Google sign-in is already running. Result: GOOGLE_SUCCESS, GOOGLE_CANCELED or GOOGLE_ERROR. Android, iOS, macOS. |
| getCredentialState(userID:String):Boolean | Asks Apple whether the Apple user ID is still authorized for the app. Returns true if the request was sent; false if userID is empty or not supported (Android). Result: APPLE_CREDENTIAL_STATE. iOS, macOS. |
class KlugAuthEvent
Package com.klug.auth · extends flash.events.Event · does not bubble, not cancelable. Fields that do not apply to an event type are empty strings, never null.
| Member | Description |
| APPLE_SUCCESS:String = "APPLE_SIGNIN_SUCCESS" | Static constant. Apple sign-in succeeded. |
| APPLE_CANCELED:String = "APPLE_SIGNIN_CANCELED" | Static constant. The user closed the Apple sheet. |
| APPLE_ERROR:String = "APPLE_SIGNIN_ERROR" | Static constant. Apple sign-in failed. |
| APPLE_CREDENTIAL_STATE:String = "APPLE_CREDENTIAL_STATE" | Static constant. Answer to getCredentialState(). |
| GOOGLE_SUCCESS:String = "GOOGLE_SIGNIN_SUCCESS" | Static constant. Google sign-in succeeded. |
| GOOGLE_CANCELED:String = "GOOGLE_SIGNIN_CANCELED" | Static constant. The user closed the Google sheet. |
| GOOGLE_ERROR:String = "GOOGLE_SIGNIN_ERROR" | Static constant. Google sign-in failed. |
| KlugAuthEvent(type:String) | Constructor; created by the extension. |
| identityToken:String | The signed JWT from Apple or Google. Send it to your server with the raw nonce. |
| authorizationCode:String | Apple only: single-use authorization code for your server. Empty for Google. |
| userID:String | Apple: stable user ID (same on all devices of the Apple ID). Google: the account e-mail. Also set on APPLE_CREDENTIAL_STATE. |
| email:String | E-mail address. Apple: only on the first authorization (may be a relay address). Google: every time. |
| givenName:String | First name. Apple: only on the first authorization. Google: every time, if the account has one. |
| familyName:String | Last name. Same rules as givenName. |
| fullName:String | Read-only. givenName and familyName joined with a space and trimmed; "" if both are empty. |
| state:String | APPLE_CREDENTIAL_STATE only: authorized, revoked, notFound, transferred or unknown. |
| errorCode:String | Error events only: see Events. |
| errorMessage:String | Error events only: a readable description from the system, for logs. |
| clone():Event | Returns a copy with all fields. |
Events
All results are KlugAuthEvents dispatched on the KlugAuth instance, asynchronously after the sheet closes.
| Type | When · fields |
| APPLE_SUCCESS | The user signed in with Apple. identityToken, authorizationCode, userID; email, givenName, familyName on the first authorization only. |
| APPLE_CANCELED | The user closed the Apple sheet. No fields. |
| APPLE_ERROR | Apple sign-in failed. errorCode: the numeric system error code as a string, for example "1000" (unknown; often a missing capability, entitlement or profile, or no Apple ID signed in on the device), "1004" (failed); "-1" if Apple returned an unexpected credential type. errorMessage: system description. |
| APPLE_CREDENTIAL_STATE | Answer to getCredentialState(). userID, state. |
| GOOGLE_SUCCESS | The user signed in with Google. identityToken, userID (the e-mail), email, givenName, familyName. |
| GOOGLE_CANCELED | The user closed the Google sheet or window. No fields. |
| GOOGLE_ERROR | Google sign-in failed. errorCode on Android: "NO_ACCOUNT" (no Google account on the device), a Credential Manager error type string, "-1" (unexpected credential type), "-2" (token could not be read), "-3" (sheet could not start). On iOS / macOS: "AUTH" (Google returned an error or no authorization code), "TOKEN" (completing the login failed, for example no network), or the numeric code of the system sign-in window. errorMessage: description. |
Troubleshooting
APPLE_ERROR with code 1000 right away.
Check, in this order: Sign in with Apple is enabled on the App ID; the provisioning profile was created after that; the entitlement com.apple.developer.applesignin is in the app (iOS: <Entitlements>, macOS: signing entitlements); the device is signed in to an Apple ID with two-factor authentication.
The e-mail and name are empty after the first test.
Apple sends them only on the first authorization of the app for that Apple ID. To test again, remove your app from the list of apps using Sign in with Apple in the Apple ID settings of the device. In production, store them on the server on the first sign-in; the e-mail is always in the token.
Android: GOOGLE_ERROR after choosing an account, or no account sheet.
Almost always the Android OAuth client: the package name must match the installed app exactly (including the air. prefix if your build uses it), and the SHA-1 must be that of the certificate the installed app is signed with. Store installs with Play App Signing need the SHA-1 of the app signing key from the Play Console. Also check that you pass the web client ID, not the Android one.
Android: NO_ACCOUNT.
The device has no Google account. Ask the user to add one in the system settings, or offer another sign-in method.
iOS / macOS: signInWithGoogle returns false.
The client ID must be an iOS client ID ending in .apps.googleusercontent.com. A web client ID does not work in the system sign-in window. False is also returned while a Google sign-in is still open.
iOS / macOS: GOOGLE_ERROR "AUTH" or "TOKEN".
"AUTH": Google refused the request, typically a wrong or deleted client ID, or a consent screen in testing mode with a user who is not listed. "TOKEN": the login could not be completed, typically no network; on a sandboxed Mac app also check com.apple.security.network.client.
The server rejects the token: wrong audience.
Android tokens carry the web client ID as audience, iOS / macOS tokens the iOS client ID. Accept both on the server. Apple tokens carry the bundle ID of the app that signed in; a native Mac build with its own bundle ID needs that ID on the list too.
The server rejects the token: nonce mismatch.
Send the raw nonce. For Apple, the server must compare the token's nonce with SHA-256 (lower-case hex) of it; for Google, with the raw value itself. Create a new nonce for every attempt and keep it until the result arrives.
Sign in with Apple on Android?
isSupported is false on Android; signInWithApple() and getCredentialState() return false there. Apple offers a web-based flow for other platforms, which needs a page on your server; it is not part of this extension.
"KlugAuth is a singleton" error.
Use KlugAuth.getInstance() everywhere instead of new KlugAuth().
Changelog
| 1.2.0 · Oct 2026 | Sign in with Google on iOS and macOS (and the iPad app on a Mac), in the system sign-in window, without a Google SDK. |
| 1.1.0 · Oct 2026 | Sign in with Google on Android with the system account sheet (Credential Manager). New: isGoogleSupported, signInWithGoogle() and the GOOGLE_* events. |
| 1.0.0 · Sep 2026 | First release: Sign in with Apple on iOS 13+, macOS 10.15+ and the iPad app on a Mac; nonce helper; Apple credential state check. |