PHPackages                             shokanshi/singpass-myinfo - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. shokanshi/singpass-myinfo

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

shokanshi/singpass-myinfo
=========================

Laravel Socialite Provider For Singpass MyInfo v5

v2.0.3(1mo ago)112[2 PRs](https://github.com/shokanshi/singpass-myinfo/pulls)MITPHPPHP ^8.3CI passing

Since Oct 30Pushed 1mo agoCompare

[ Source](https://github.com/shokanshi/singpass-myinfo)[ Packagist](https://packagist.org/packages/shokanshi/singpass-myinfo)[ Docs](https://github.com/shokanshi/singpass-myinfo)[ GitHub Sponsors](https://github.com/shokanshi)[ RSS](/packages/shokanshi-singpass-myinfo/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (9)Dependencies (16)Versions (16)Used By (0)

Laravel Socialite Provider For Singpass MyInfo v5 (FAPI 2.0)
============================================================

[](#laravel-socialite-provider-for-singpass-myinfo-v5-fapi-20)

[![Latest Version on Packagist](https://camo.githubusercontent.com/55122b10168d6e531845dfcd5baa041ce549d3a0259c645986137aa67fc146c5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73686f6b616e7368692f73696e67706173732d6d79696e666f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/shokanshi/singpass-myinfo)[![Total Downloads](https://camo.githubusercontent.com/53c020503294d6137222e04674ed56b89f6a0ba48283d05112eac763d2ebfcb1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73686f6b616e7368692f73696e67706173732d6d79696e666f2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/shokanshi/singpass-myinfo)[![PHP Version](https://camo.githubusercontent.com/c455a835b64fd24c963fd1ec24821105b6f2a996fc158049f20424e7d308de55/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f73686f6b616e7368692f73696e67706173732d6d79696e666f)](https://packagist.org/packages/shokanshi/singpass-myinfo)

The purpose of this Laravel package is to make it very easy for PHP (8.3+) developers to integrate [Singpass MyInfo v5 (FAPI 2.0)](https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide).

Singpass OpenID Provider Configuration:
---------------------------------------

[](#singpass-openid-provider-configuration)

EnvironmentDiscovery EndpointStagingProductionIf you encounter the error message `The client assertion is invalid.` when you are testing with your existing application on Singpass staging environment, check to ensure that your application is not missing a `purpose`. If it still doesn't work, you can try creating a new application on Singpass Developer Portal.

Requirements
------------

[](#requirements)

- PHP ≥ 8.3
- Laravel ≥ 11.0

Support Me
----------

[](#support-me)

A sponsor will be greatly appreciated but not required to use this package. 😊

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require shokanshi/singpass-myinfo
```

Configuration
-------------

[](#configuration)

You can publish the config file with:

```
php artisan vendor:publish --tag="singpass-myinfo-config"
```

This is the content of the published config file: ```php return \[ // default to Singpass staging 'openid\_discovery\_endpoint' =&gt; env('SINGPASS\_OPENID\_DISCOVERY\_ENDPOINT', ''), ```
'client_id' => env('SINGPASS_CLIENT_ID'),

// this setting is here because socialite requires it to be defined. SingpassProvider will always overwrite it to route('singpass.callback')
'redirect' => env('SINGPASS_REDIRECT_URI'),

// the private key file that your application will be used for signing
'signing_private_key_passphrase' => env('SINGPASS_SIGNING_PRIVATE_KEY_PASSPHRASE', ''),
'signing_private_key_file' => env('SINGPASS_SIGNING_PRIVATE_KEY_FILE'),

// the private key file that your application will be used for decryption
'decryption_private_key_passphrase' => env('SINGPASS_DECRYPTION_PRIVATE_KEY_PASSPHRASE', ''),
'decryption_private_key_file' => env('SINGPASS_DECRYPTION_PRIVATE_KEY_FILE'),

// used by socialite. leave it empty since Singpass uses client assertion
'client_secret' => '',

// default to Singpass login if SINGPASS_SCOPES is blank. for MyInfo, define additional scopes that are space separated
// e.g. "openid uinfin name sex race dob birthcountry passportnumber"
'scopes' => env('SINGPASS_SCOPES', 'openid'),

// this is the route that will be used to redirect to Singpass login page
// you can customize this in .env file
'authorization_endpoint' => env('SINGPASS_AUTHORIZATION_ENDPOINT', 'sp/login'),

// the controller that will handle the redirection to Singpass login page
// to customize, you can replace it with your own controller in this config file
'authorization_endpoint_controller' => GetAuthorizationController::class,

// this is the route that will be called when Singpass redirects back after authentication
// you can customize this in .env file
'callback_endpoint' => env('SINGPASS_CALLBACK_ENDPOINT', 'sp/callback'),

// the controller that will handle the callback from Singpass after login
// to customize, you can replace it with your own controller in this config file
'callback_endpoint_controller' => GetCallbackController::class,

// this is the url that Singpass will call to retrieve your public jwks for signing and encryption
// you can customize this in .env file
'jwks_endpoint' => env('SINGPASS_JWKS_ENDPOINT', 'sp/jwks'),

// the controller that Singpass portal will use to retrieve your application jwks
// typically you won't want to change it unless you want to implement key rotation logic
'jwks_endpoint_controller' => GetJwksController::class,

```

\];

```

## Setting Up Private Keys

The package will attempt to load the private keys from `storage/app`.

**❌ DO NOT** store the private keys in `./storage/app/public` folder! They will be publicly accessible!

If you have not already done so, create a `secure` folder within `storage/app` in your project folder.

Create private key for signing:

```bash
openssl ecparam -name prime256v1 -genkey -noout -out ./storage/app/secure/your-singpass-signing-private.pem

```

Create private key for decryption:

```
openssl ecparam -name prime256v1 -genkey -noout -out ./storage/app/secure/your-singpass-decryption-private.pem
```

Add the following variables to your `.env` file and adjust accordingly to your app. The following is just an example.

```
# Singpass variables
SINGPASS_CLIENT_ID=

# Base folder is ./storage/app
SINGPASS_SIGNING_PRIVATE_KEY_FILE=secure/your-singpass-signing-private.pem
SINGPASS_SIGNING_PRIVATE_KEY_PASSPHRASE=

# Base folder is ./storage/app
SINGPASS_DECRYPTION_PRIVATE_KEY_FILE=secure/your-singpass-decryption-private.pem
SINGPASS_DECRYPTION_PRIVATE_KEY_PASSPHRASE=

SINGPASS_OPENID_DISCOVERY_ENDPOINT=https://stg-id.singpass.gov.sg/.well-known/openid-configuration

# for Singpass login, set openid as the only scope. Additional scopes (space separated within double quotes) will switch to MyInfo flow
SINGPASS_SCOPES="openid"

# Default routes
SINGPASS_AUTHORIZATION_ENDPOINT=sp/login
SINGPASS_CALLBACK_ENDPOINT=sp/callback
SINGPASS_JWKS_ENDPOINT=sp/jwks
```

Checking If It Work Right Out Of The Box For You
------------------------------------------------

[](#checking-if-it-work-right-out-of-the-box-for-you)

Remember to create your [Singpass application](https://docs.developer.singpass.gov.sg/docs/getting-started/create-singpass-application) at [Singpass Developer Portal](https://developer.singpass.gov.sg/) before you proceed to test.

Assuming you are using the default setup and filled up the values in `.env` file:

1. Test your jwks endpoint to see if Singpass is able to access it:

```
https://your-company.com/sp/jwks

```

2. Test if it redirects to Singpass auth endpoint:

```
https://your-company.com/sp/login

```

Response Returned From Singpass Login / MyInfo
----------------------------------------------

[](#response-returned-from-singpass-login--myinfo)

Even though Singpass Login and MyInfo have 2 separate structure for returning user profile, this package will format Login structure into MyInfo structure so that it makes it less confusing to retrieve the information.

Login / MyInfo sample data:

For data catalog of MyInfo:

Routes
------

[](#routes)

There are three default routes that you can customize, namely: ```
SINGPASS_AUTHORIZATION_ENDPOINT=sp/login
SINGPASS_CALLBACK_ENDPOINT=sp/callback
SINGPASS_JWKS_ENDPOINT=sp/jwks
```

You can access the routes via name in your Laravel codes:

```
route('singpass.login');
route('singpass.callback');
route('singpass.jwks');
```

### Custom Routes

[](#custom-routes)

If you prefer the authentication url to be `https://your-company.com/sp/auth`, you can update `SINGPASS_AUTHORIZATION_URL` to `sp/auth`:

```
SINGPASS_AUTHORIZATION_URL=sp/auth
```

Custom Controllers
------------------

[](#custom-controllers)

You can customize the default controller via the `singpass-myinfo.php` config file. ```
'authorization_endpoint_controller' => GetAuthorizationController::class,
'callback_endpoint_controller' => GetCallbackController::class,
'jwks_endpoint_controller' => GetJwksController::class,
```

### Example:

[](#example)

To create an authentication controller that will switch between local and production environment

```
php artisan make:controller MySingpassAuthController
```

In `singpass-myinfo.php` config file:

```
'authorization_endpoint_controller' => MySingpassAuthController::class,
```

In `MySingpassAuthController.php`:

```
class MySingpassAuthController extends Controller
{
    public function __invoke(Request $request)
    {
        return singpass()
        ->when(app()->environment('local'), function($singpass) {
            $singpass
                ->setClientId('staging client id')
                ->setOpenIdDiscoveryUrl('https://stg-id.singpass.gov.sg/.well-known/openid-configuration')
                ->addSigningKey(Storage::disk('local')->get('stage_signing_key_1.pem'))
                ->addDecryptionKey(Storage::disk('local')->get('stage_decryption_key_1.pem'));
        })
        ->when(app()->environment('production'), function($singpass) {
            $singpass
                ->setClientId('production client id')
                ->setOpenIdDiscoveryUrl('https://id.singpass.gov.sg/.well-known/openid-configuration')
                ->addSigningKey(Storage::disk('local')->get('prod_signing_key_1.pem'))
                ->addDecryptionKey(Storage::disk('local')->get('prod_decryption_key_1.pem'));
        })
        ->redirect();
    }
}
```

**ℹ️ Note:**

1. For the above example, the same customization has to be applied to `callback_endpoint_controller` and `jwks_endpoint_controller` since the endpoint is now based on environment of the application.
2. The above is just an example to illustrate how you may customize the controllers.

 Using the Socialite Provider
----------------------------

[](#using-the-socialite-provider)

Base methods to start using the Socialite provider ### `singpass(): SingpassProvider`

[](#singpass-singpassprovider)

A helper method that return the SingpassProvider Socialite object.

In the event where `singpass()` is not available (likely in conflict with another helper method in your project), you can still access the Socialite by calling `Socialite::driver('singpass')`.

---

### `user(): \Laravel\Socialite\Contracts\User`

[](#user-laravelsocialitecontractsuser)

Return the Socialite user object.

---

### `redirect(): \Illuminate\Http\RedirectResponse`

[](#redirect-illuminatehttpredirectresponse)

Redirect the user of the application to the provider's authentication screen.

To retrieve the redirect url, you can call `singpass()->redirect()->getTargetUrl()`.

---

Methods Available
-----------------

[](#methods-available)

Login Apps Only ### `setAuthenticationContextType(string $context): self`

[](#setauthenticationcontexttypestring-context-self)

Required if you are using Singpass Login app flow. For possible values, refer to:

[https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide/1.-authorization-request#possible-authentication\_context\_type-values](https://docs.developer.singpass.gov.sg/docs/technical-specifications/integration-guide/1.-authorization-request#possible-authentication_context_type-values)

#### Parameters

[](#parameters)

NameTypeDescriptionDefault`$context``string`A value selected from a predefined list describing the type of transaction that your user is performing the authentication for. This will be used for anti-fraud purposes.*required only for Singpass login and not allowed for MyInfo*---

### `setAuthenticationContextMessage(string $message): self`

[](#setauthenticationcontextmessagestring-message-self)

Optional string with a maximum length of 100 characters. Allowed only for Login apps.

NameTypeDescriptionDefault`$message``string`A string value providing context on what users are performing authentication for. This will be displayed to users when they are performing authentication.*optional, allowed only for Login apps and max 100 characters*Login &amp; MyInfo Apps If you have a multitenancy application and would like to allow onboarding of individual tenant onto Singpass, the following methods will be useful to you. You can setup custom controllers (like the [example](#example) above) to handle the aspect of multitenancy with them.

---

### `setClientId(string $clientId): self`

[](#setclientidstring-clientid-self)

Overwrite the value of `SINGPASS_CLIENT_ID` defined in the `.env` file when called.

#### Parameters

[](#parameters-1)

NameTypeDescriptionDefault`$clientId``string`Singpass client id*required*---

### `setOpenIdDiscoveryUrl(string $url): self`

[](#setopeniddiscoveryurlstring-url-self)

Overwrite the value of `SINGPASS_DISCOVERY_ENDPOINT` defined in the `.env` file when called.

#### Parameters

[](#parameters-2)

NameTypeDescriptionDefault`$url``string`Singpass openid discovery endpoint*required*---

### `setRedirectUrl(string $redirectUrl): self`

[](#setredirecturlstring-redirecturl-self)

Overwrite the value of `SINGPASS_REDIRECT_URI` defined in the `.env` file when called. Useful when your application have different redirects based on certain business logic.

#### Parameters

[](#parameters-3)

NameTypeDescriptionDefault`$redirectUrl``string`Singpass callback endpoint*required*---

### `addSigningKey(string $keyContent, ?string $passphrase): self`

[](#addsigningkeystring-keycontent-string-passphrase-self)

Add a new private key to the collection and overwrite the value of `SINGPASS_SIGNING_PRIVATE_KEY_FILE` defined in the `.env` file when called.

#### Parameters

[](#parameters-4)

NameTypeDescriptionDefault`$keyContent``string`The content of the private key pem file that will be used for signing*required*`$passphrase``string`The passphrase for the pem file if it is encrypted---

### `addSigningKeyFromJsonObject(string $json): self`

[](#addsigningkeyfromjsonobjectstring-json-self)

Add a new private key to the collection and overwrite the value of `SINGPASS_SIGNING_PRIVATE_KEY_FILE` defined in the `.env` file when called.

#### Parameters

[](#parameters-5)

NameTypeDescriptionDefault`$json``string`Json encoded string of the private JWK that will be used for signing*required*#### Sample json object from [Singpass Demo](https://github.com/singpass/demo-app) for signing:

[](#sample-json-object-from-singpass-demo-for-signing)

```
{
    "alg": "ES256",
    "kty": "EC",
    "x": "tqG7PiAPD0xTBKdxDd4t8xAjJleP3Szw1CZiBjogmoc",
    "y": "256TjvubWV-x-C8lptl7eSbMa7pQUXH9LY1AIHUGINk",
    "crv": "P-256",
    "d": "PgL1UKVpvg_GeKdxV-oUEPIDhGBP2YYZLGiZ5HXDZDI",
    "use": "sig",
    "kid": "my-sig-key"
}
```

---

### `addDecryptionKey(string $keyContent, ?string $passphrase): self`

[](#adddecryptionkeystring-keycontent-string-passphrase-self)

Add a new private key to the collection and overwrite the value of `SINGPASS_DECRYPTION_PRIVATE_KEY_FILE` defined in the `.env` file when called.

#### Parameters

[](#parameters-6)

NameTypeDescriptionDefault`$keyContent``string`The content of the private key pem file that will be used for decryption*required*`$passphrase``string`The passphrase for the pem file if it is encrypted---

### `addDecryptionKeyFromJsonObject(string $json): self`

[](#adddecryptionkeyfromjsonobjectstring-json-self)

Add a new private key to the collection and overwrite the value of `SINGPASS_DECRYPTION_PRIVATE_KEY_FILE` defined in the `.env` file when called.

#### Parameters

[](#parameters-7)

NameTypeDescriptionDefault`$json``string`Json encoded string of the private JWK that will be used for decryption*required*#### Sample json object from [Singpass Demo](https://github.com/singpass/demo-app) for decryption:

[](#sample-json-object-from-singpass-demo-for-decryption)

```
{
    "alg": "ECDH-ES+A256KW",
    "kty": "EC",
    "x": "_TSrfW3arG1Ebc8pCyT-r5lAFvCh_rJvC5HD5-y8yvs",
    "y": "Sr2vpuU6gzdUiXddGnRJIroXCfdameaR1mgU49H5h9A",
    "crv": "P-256",
    "d": "AEabUwi3VjOOfiyoOtSGrqpl8cfhcUhNtj-xh1l-UYE",
    "use": "enc",
    "kid": "my-enc-key"
}
```

---

### `generateJwksForSingpassPortal(): array`

[](#generatejwksforsingpassportal-array)

Return an array of public keys that will be json encoded and consumed by Singpass.

---

### `when($value, ?callable $callback = null, ?callable $default = null): self`

[](#whenvalue-callable-callback--null-callable-default--null-self)

The `when()` method allows you to conditionally execute a closure (a function) if a given condition evaluates to true. Its primary purpose is to apply modifications to an object within a method chain, based on a dynamic condition, without having to break the chain into a traditional if statement.

In short: It's an if statement that you can use inside a method chain. See the [Example](#example) above.

#### Parameters

[](#parameters-8)

NameTypeDescriptionDefault`$value``boolean`Apply the callback if the given "value" is (or resolves to) truthy*required*`$callback``callable`The callback when $value resolved to true`$default``callable`The callback when $value resolved to false and this parameter is definedAdvanced Usage Example (Multitenancy + Key Rotation)
----------------------------------------------------

[](#advanced-usage-example-multitenancy--key-rotation)

Singpass recommends [key rotation](https://docs.developer.singpass.gov.sg/docs/upcoming-changes/fapi-2.0-authentication-api/technical-concepts/json-web-key-sets-jwks) on a yearly basis.

The following is an example to illustrate a more advance use case for this package that handles multitenancy and key rotation.

[Spatie multitenancy](https://github.com/spatie/laravel-multitenancy) package will be used for illustration.

### Custom fields added to Tenant table

[](#custom-fields-added-to-tenant-table)

NameTypeDescriptionDefault`singpass_client_id``varchar(255)`Singpass client id*required*`singpass_openid_discovery_endpoint``varchar(255)`Singpass openid discovery endpoint id*required*`singpass_scopes``text`Space separated Singpass scopes`openid`### New Table: tenant\_private\_keys

[](#new-table-tenant_private_keys)

NameTypeDescriptionDefault`id``bigint`Primary key*required*`tenant_id``bigint`Foreign key*required*`provider``varchar(50)`e.g. singpass*required*`type``varchar(50)`e.g. signing or decryption*required*`key_content``text`Encrypted pem file content*required*`passphrase``varchar(255)`Encrypted passphrase for signing key*required*`valid_from``datetime`The date the key is valid from*required*`valid_to``datetime`The date the key is valid to*required*```
class MySingpassJwksEndpointController extends Controller
{
    public function __invoke(Request $request)
    {
        $tenant = Tenant::current();

        singpass()
            ->setClientId($tenant->singpass_client_id)
            ->setOpenIdDiscoveryUrl($tenant->singpass_openid_discovery_endpoint)
            ->setScopes([$tenant->singpass_scopes]);

        foreach ($tenant->singpassPrivateKeys() as $key) {
            singpass()
                ->when(Carbon::now()->between($key->valid_from, $key->valid_to), function($singpass) use ($key) {
                    $singpass
                        ->when($key->type === 'signing', function($singpass) use ($key) {
                            $singpass->addSigningKey($key->key_content, $key->passphrase);
                        })
                        ->when($key->type === 'decryption', function($singpass) use ($key) {
                            $singpass->addDecryptionKey($key->key_content, $key->passphrase);
                        });
                });
        }

        return response()->json(json_encode(singpass()->generateJwksForSingpassPortal()));
    }
}
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

The code of this package is heavily influenced by the code shown in [Laravel Socialite - Singpass](https://leeliwei930.medium.com/integrating-singpass-login-api-with-laravel-socialite-provider-part-1-onboarding-setup-210d7fa0f31f).

You will also find some code reference from [Accredifysg/SingPass-Login](https://github.com/Accredifysg/SingPass-Login/) in this package.

- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance89

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity58

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 88.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~17 days

Total

9

Last Release

55d ago

Major Versions

v1.1.1 → v2.0.02026-01-27

PHP version history (2 changes)v1.0.0PHP ^8.2

v1.0.1PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/6763dd6dc7e38942f65cdd9b3bda6ccf728d89725774e8edb0db8848220fc080?d=identicon)[shokanshi](/maintainers/shokanshi)

---

Top Contributors

[![shokanshi](https://avatars.githubusercontent.com/u/10794342?v=4)](https://github.com/shokanshi "shokanshi (102 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (7 commits)")[![tegupaws](https://avatars.githubusercontent.com/u/152943386?v=4)](https://github.com/tegupaws "tegupaws (5 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

laravelMyInfoSingPassMyInfo v5

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/shokanshi-singpass-myinfo/health.svg)

```
[![Health](https://phpackages.com/badges/shokanshi-singpass-myinfo/health.svg)](https://phpackages.com/packages/shokanshi-singpass-myinfo)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k89.8M1.0k](/packages/spatie-laravel-permission)[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[dutchcodingcompany/filament-socialite

Social login for Filament through Laravel Socialite

213914.9k9](/packages/dutchcodingcompany-filament-socialite)[jeffgreco13/filament-breezy

A custom package for Filament with login flow, profile and teams support.

1.0k1.7M41](/packages/jeffgreco13-filament-breezy)[spatie/laravel-login-link

Quickly login to your local environment

4381.2M1](/packages/spatie-laravel-login-link)[ryangjchandler/laravel-cloudflare-turnstile

A simple package to help integrate Cloudflare Turnstile.

438896.6k2](/packages/ryangjchandler-laravel-cloudflare-turnstile)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
