<?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: Binuka Jayaweera</title>
    <description>The latest articles on DEV Community by Binuka Jayaweera (@binukajayaweera).</description>
    <link>https://dev.to/binukajayaweera</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4098697%2Fc7c15608-ea4e-488e-8e6b-1d35c9b38258.jpeg</url>
      <title>DEV Community: Binuka Jayaweera</title>
      <link>https://dev.to/binukajayaweera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/binukajayaweera"/>
    <language>en</language>
    <item>
      <title>Safer Sign in with Apple Library for PHP</title>
      <dc:creator>Binuka Jayaweera</dc:creator>
      <pubDate>Fri, 28 Aug 2026 10:06:52 +0000</pubDate>
      <link>https://dev.to/binukajayaweera/safer-sign-in-with-apple-library-for-php-1460</link>
      <guid>https://dev.to/binukajayaweera/safer-sign-in-with-apple-library-for-php-1460</guid>
      <description>&lt;h2&gt;
  
  
  A defensive, framework-neutral implementation with bounded JWKS fetching, modern JWT verification, full OAuth lifecycle support, and Expo/React Native integration
&lt;/h2&gt;

&lt;p&gt;Authentication code has an awkward property: when it works, nobody notices it. When it fails, it can block every login—or quietly accept something it should not.&lt;/p&gt;

&lt;p&gt;I recently reviewed a PHP application whose Sign in with Apple flow depended on an old library. The package had helped many developers, but its implementation reflected a different time in the PHP ecosystem. It included a frozen copy of JWT code and downloaded Apple's public signing keys during every login using an unbounded network call.&lt;/p&gt;

&lt;p&gt;That created three practical problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A slowdown at Apple could occupy every available PHP-FPM worker.&lt;/li&gt;
&lt;li&gt;An unknown signing-key ID could fall through to the wrong array entry instead of producing a clear failure.&lt;/li&gt;
&lt;li&gt;The bundled JWT implementation could not receive normal upstream security updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The application's audience validation was already correct, so this was not an authentication-bypass discovery. It was an availability, maintainability, and software-supply-chain problem.&lt;/p&gt;

&lt;p&gt;I wanted a modern alternative that treated those concerns as part of authentication—not as optional infrastructure around it. That became &lt;strong&gt;Safe Apple Sign In for PHP&lt;/strong&gt;, now available as the open-source Composer package &lt;a href="https://packagist.org/packages/binuka200/apple-sign-in" rel="noopener noreferrer"&gt;&lt;code&gt;binuka200/apple-sign-in&lt;/code&gt;&lt;/a&gt; and on &lt;a href="https://github.com/binuka200/apple-sign-in-php" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The project is released under the permissive MIT license and is open to community contributions. PHP developers, security engineers, framework maintainers, and mobile developers are welcome to review the design, report bugs, propose improvements, add integrations, improve documentation, and submit pull requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require binuka200/apple-sign-in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source is available on &lt;a href="https://github.com/binuka200/apple-sign-in-php" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; under the MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden network call on the login path
&lt;/h2&gt;

&lt;p&gt;Apple signs identity tokens with private keys and publishes the corresponding public keys as a JWKS document. A PHP backend needs those public keys to verify that a token really came from Apple.&lt;/p&gt;

&lt;p&gt;The simplest implementation fetches Apple's JWKS endpoint whenever a user signs in. It is also a dangerous default.&lt;/p&gt;

&lt;p&gt;Imagine a PHP-FPM replica with five workers. If five anonymous requests initiate Apple login while Apple's key endpoint is slow, and every request waits for the default socket timeout, the entire replica can become unavailable. The attacker does not need a valid Apple account because the expensive work happens before authentication succeeds.&lt;/p&gt;

&lt;p&gt;The new library makes JWKS retrieval deliberately bounded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network timeout: two seconds&lt;/li&gt;
&lt;li&gt;Maximum request duration: four seconds&lt;/li&gt;
&lt;li&gt;One controlled retry&lt;/li&gt;
&lt;li&gt;PSR-16 shared caching&lt;/li&gt;
&lt;li&gt;One-hour fresh-key lifetime&lt;/li&gt;
&lt;li&gt;A 24-hour stale-key fallback during an upstream outage&lt;/li&gt;
&lt;li&gt;A refresh cooldown for unknown key IDs&lt;/li&gt;
&lt;li&gt;An optional lock to prevent multiple PHP-FPM workers refreshing simultaneously
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SafeApple\SignIn\AppleJwksProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SafeApple\SignIn\Support\FlockRefreshLock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$jwks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppleJwksProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$sharedPsr16Cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;refreshLock&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FlockRefreshLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'/run/lock/my-app-apple-jwks.lock'&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;In production, &lt;code&gt;$sharedPsr16Cache&lt;/code&gt; should normally be backed by Redis or another cache shared by all application workers. The included in-memory cache is intended for tests and single-process examples.&lt;/p&gt;

&lt;p&gt;This design does not pretend the network cannot fail. It limits the damage when it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification is more than checking a signature
&lt;/h2&gt;

&lt;p&gt;A valid RSA signature only proves that Apple signed a token. It does not prove that the token was issued for &lt;em&gt;your&lt;/em&gt; application or belongs to the authorization flow currently in progress.&lt;/p&gt;

&lt;p&gt;The verifier therefore checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The RS256 algorithm and Apple signature&lt;/li&gt;
&lt;li&gt;The exact Apple issuer&lt;/li&gt;
&lt;li&gt;Issued-at and expiry claims, with a bounded clock leeway&lt;/li&gt;
&lt;li&gt;A non-empty subject&lt;/li&gt;
&lt;li&gt;The complete audience set&lt;/li&gt;
&lt;li&gt;The authorized party (&lt;code&gt;azp&lt;/code&gt;) when applicable&lt;/li&gt;
&lt;li&gt;The expected nonce&lt;/li&gt;
&lt;li&gt;The authorization-code hash (&lt;code&gt;c_hash&lt;/code&gt;) for hybrid authorization responses&lt;/li&gt;
&lt;li&gt;A strict token-size limit before decoding
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SafeApple\SignIn\AppleIdentityTokenVerifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppleIdentityTokenVerifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$jwks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'com.example.ios'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'com.example.web'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;leeway&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$verifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verifyAuthorizationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;identityToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$challenge&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nonce&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;The audience rule is intentionally strict. If a token contains multiple audiences, every audience must be trusted and &lt;code&gt;azp&lt;/code&gt; must identify an allowed member of that set. Weakening audience checks to make a configuration error disappear is exactly the wrong trade-off on a login endpoint.&lt;/p&gt;

&lt;p&gt;The stable account key is Apple's &lt;code&gt;sub&lt;/code&gt; claim—not the email address. A user can choose Apple's private email relay, and email-related data may change. The subject is the provider identity your user record should retain.&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete server lifecycle, not only JWT decoding
&lt;/h2&gt;

&lt;p&gt;Sign in with Apple involves more than decoding an identity token. The package covers the server-side lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating ES256 client secrets from Apple's &lt;code&gt;.p8&lt;/code&gt; private key&lt;/li&gt;
&lt;li&gt;Building state- and nonce-protected authorization URLs&lt;/li&gt;
&lt;li&gt;Parsing and validating authorization callbacks&lt;/li&gt;
&lt;li&gt;Exchanging single-use authorization codes&lt;/li&gt;
&lt;li&gt;Validating refreshed identity tokens&lt;/li&gt;
&lt;li&gt;Revoking access and refresh tokens&lt;/li&gt;
&lt;li&gt;Parsing the name and email supplied during first authorization&lt;/li&gt;
&lt;li&gt;Verifying Apple's server-to-server account notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the central callback flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SafeApple\SignIn\AppleAuthorizationResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SafeApple\SignIn\LoginChallenge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple_challenge'&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="k"&gt;unset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'apple_challenge'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// Consume once.&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="nb"&gt;is_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stored&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Apple login session expired.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoginChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$stored&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'state'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nv"&gt;$stored&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nonce'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AppleAuthorizationResponse&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$callbackIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$verifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verifyAuthorizationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;identityToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$challenge&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;exchangeAuthorizationCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$callback&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'https://example.com/auth/apple/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$exchangedIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$verifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;identityToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$challenge&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nonce&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="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$callbackIdentity&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$exchangedIdentity&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subject&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuntimeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Apple identities do not match.'&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;The authorization-code exchange is not automatically retried. Codes are single-use, so blindly replaying one after an ambiguous network failure can create confusing behavior. Safe retry policy depends on the semantics of the operation, not merely on whether an HTTP request failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server notifications without hidden side effects
&lt;/h2&gt;

&lt;p&gt;Apple can send account events such as revoked consent, account deletion, or changes to private-email forwarding. The library verifies and returns a typed event, but it does not mark that event as processed.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;p&gt;A cache interface cannot atomically combine “this notification has not been processed” with “apply this account change.” If a library records the event first and your database update then fails, Apple's retry may be discarded even though the account change never happened.&lt;/p&gt;

&lt;p&gt;Instead, applications should store Apple's JWT ID under a database &lt;code&gt;UNIQUE&lt;/code&gt; constraint and apply the account change in the same transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$notifications&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'payload'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nf"&gt;processAppleEventOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Apply the corresponding account operation here.&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;The library owns cryptographic verification. Your application owns durable idempotency and business behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it with Expo and React Native
&lt;/h2&gt;

&lt;p&gt;Yes, the PHP package can be the backend for a mobile app.&lt;/p&gt;

&lt;p&gt;On iOS, Expo's &lt;code&gt;expo-apple-authentication&lt;/code&gt; module obtains the Apple credential from the operating system. The app sends the identity token and authorization code to PHP, and PHP performs the trusted verification and code exchange. Private keys and client secrets must never be bundled into the mobile application.&lt;/p&gt;

&lt;p&gt;The recommended shape is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expo / React Native       PHP backend                 Apple
        |                      |                         |
        |-- request challenge-&amp;gt;|                         |
        |&amp;lt;- state + nonce -----|                         |
        |---------------- native Apple authorization --&amp;gt;|
        |&amp;lt;- credential ---------------------------------|
        |-- token, code, challenge ID ------------------&amp;gt;|
        |                      |-- verify/exchange ------&amp;gt;|
        |                      |&amp;lt;- tokens/identity -------|
        |&amp;lt;- application session|                         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the state and nonce on the server, associate them with a short-lived opaque challenge ID, and consume the challenge exactly once. The server should then verify the nonce, signature, issuer, audience, &lt;code&gt;azp&lt;/code&gt;, time claims, and &lt;code&gt;c_hash&lt;/code&gt; before creating its own application session.&lt;/p&gt;

&lt;p&gt;Expo's native Apple Authentication module currently targets iOS and tvOS, not Android or web. Android and browser clients should use Apple's web authorization flow, while keeping token verification and secrets on PHP. The repository includes a complete &lt;a href="https://github.com/binuka200/apple-sign-in-php/blob/main/docs/mobile-apps.md" rel="noopener noreferrer"&gt;Expo and React Native guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the package deliberately does not do
&lt;/h2&gt;

&lt;p&gt;Authentication libraries become risky when they quietly take ownership of application policy. This package does not create users, database records, framework routes, cookies, or sessions.&lt;/p&gt;

&lt;p&gt;Your application is still responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consuming login challenges once&lt;/li&gt;
&lt;li&gt;Encrypting refresh tokens at rest&lt;/li&gt;
&lt;li&gt;Creating secure application sessions&lt;/li&gt;
&lt;li&gt;Applying rate limits at the public endpoint&lt;/li&gt;
&lt;li&gt;Escaping first-login profile text before output&lt;/li&gt;
&lt;li&gt;Mapping detailed internal exceptions to generic public errors&lt;/li&gt;
&lt;li&gt;Processing notifications transactionally&lt;/li&gt;
&lt;li&gt;Avoiding logs that contain tokens, authorization codes, nonces, or private keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Framework examples are provided for plain PHP, Laravel, and Symfony, but the core remains framework-neutral.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release confidence and honest boundaries
&lt;/h2&gt;

&lt;p&gt;Version &lt;code&gt;v0.1.0&lt;/code&gt; supports PHP 8.1 through 8.5. Its CI pipeline runs linting, PHPStan level 8, PHPUnit tests, Composer advisory checks, and a lowest-supported-dependency build. GitHub Actions are pinned by commit SHA to reduce workflow supply-chain drift.&lt;/p&gt;

&lt;p&gt;Those checks are valuable, but they are not the same as an independent professional security audit. This is a new open-source release, and careful review from PHP and identity specialists is welcome.&lt;/p&gt;

&lt;p&gt;If you find a vulnerability, please use the repository's private vulnerability-reporting channel rather than opening a public issue with exploit details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source and open for contributions
&lt;/h2&gt;

&lt;p&gt;Safe Apple Sign In for PHP is not intended to be a closed, one-person implementation. Authentication benefits from review by people with different production environments, threat models, and framework experience.&lt;/p&gt;

&lt;p&gt;Contributions are welcome, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security and correctness reviews&lt;/li&gt;
&lt;li&gt;Reproducible bug reports and regression tests&lt;/li&gt;
&lt;li&gt;Laravel, Symfony, and other framework integrations&lt;/li&gt;
&lt;li&gt;Expo and React Native examples&lt;/li&gt;
&lt;li&gt;Cache and distributed-lock adapters&lt;/li&gt;
&lt;li&gt;Documentation and accessibility improvements&lt;/li&gt;
&lt;li&gt;Compatibility testing across supported PHP versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can start by reading the project's &lt;a href="https://github.com/binuka200/apple-sign-in-php/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contribution guide&lt;/a&gt;, opening an issue, or submitting a pull request. For suspected vulnerabilities, please use GitHub's private vulnerability-reporting feature instead of publishing exploit details in a public issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it and help improve it
&lt;/h2&gt;

&lt;p&gt;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;composer require binuka200/apple-sign-in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/binuka200/apple-sign-in-php" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://packagist.org/packages/binuka200/apple-sign-in" rel="noopener noreferrer"&gt;Packagist package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/binuka200/apple-sign-in-php/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Contribution guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/binuka200/apple-sign-in-php/blob/main/docs/mobile-apps.md" rel="noopener noreferrer"&gt;Mobile integration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/signinwithapplerestapi" rel="noopener noreferrer"&gt;Apple Sign in REST API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.expo.dev/versions/latest/sdk/apple-authentication/" rel="noopener noreferrer"&gt;Expo Apple Authentication documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this solves a problem in your project, a GitHub star, issue, test case, documentation improvement, or pull request would help the open-source library mature.&lt;/p&gt;

&lt;p&gt;Authentication should fail closed, fail quickly, and remain maintainable. That is the standard this project is trying to meet.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>webdev</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
