July 30, 2026 — The unofficial Laracon US Day 3. Get your ticket to The Vibes
Blog

SecureStorage: Keychain & Encrypted Storage Made Simple

You've got secrets to keep. API tokens. Refresh tokens. Encryption keys.

Stuff that absolutely cannot live in plain text.

On iOS, there's the Keychain. On Android, there's the Keystore. Both are hardware-backed, encrypted by the OS.

NativePHP gives you one API for both.

In this post:

The Basics#

PHP (Livewire)#

Copied!
use Native\Mobile\Facades\SecureStorage;
 
// Store a secret
SecureStorage::set('api_token', 'sk_live_abc123xyz');
 
// Retrieve it
$token = SecureStorage::get('api_token');
 
// Delete it
SecureStorage::delete('api_token');

JavaScript (Vue/React/Inertia)#

Copied!
import { SecureStorage } from '@nativephp/mobile';
 
// Store a secret
await SecureStorage.set('api_token', 'sk_live_abc123xyz');
 
// Retrieve it
const { value } = await SecureStorage.get('api_token');
 
// Delete it
await SecureStorage.delete('api_token');

That's it. The value is encrypted by the OS using hardware-backed security.

Storing and Retrieving#

PHP (Livewire)#

Copied!
// Store credentials
SecureStorage::set('credentials', json_encode([
'username' => $username,
'refresh_token' => $refreshToken,
]));
 
// Retrieve with null check
$token = SecureStorage::get('auth_token');
if ($token === null) {
return redirect('/login');
}
 
// Clean up on logout
public function logout()
{
SecureStorage::delete('auth_token');
SecureStorage::delete('refresh_token');
auth()->logout();
}

JavaScript (Vue/React/Inertia)#

Copied!
import { SecureStorage } from '@nativephp/mobile';
 
// Store credentials
await SecureStorage.set('credentials', JSON.stringify({
username,
refreshToken
}));
 
// Retrieve with null check
const { value } = await SecureStorage.get('auth_token');
if (!token) {
router.push('/login');
}
 
// Clean up on logout
const logout = async () => {
await SecureStorage.delete('auth_token');
await SecureStorage.delete('refresh_token');
};

Best Practices#

1. Use Descriptive Keys#

Copied!
// Bad
SecureStorage::set('t', $token);
 
// Good
SecureStorage::set('stripe_api_key', $key);

2. Clean Up on Logout#

Copied!
$keysToDelete = ['access_token', 'refresh_token', 'api_key'];
 
foreach ($keysToDelete as $key) {
SecureStorage::delete($key);
}

3. What to Store (and What Not To)#

DO Store:

  • Authentication tokens
  • API keys
  • Encryption keys
  • OAuth secrets

DON'T Store:

  • Large data (Keychain isn't a database)
  • Non-sensitive preferences
  • Frequently accessed data (has overhead)

4. Combine with Biometrics#

Copied!
// PHP
public function viewSecrets()
{
Biometrics::prompt()->id('vault-unlock');
}
 
#[On('native:' . Completed::class)]
public function handleAuth(bool $success, string $id)
{
if ($id === 'vault-unlock' && $success) {
$this->secrets = SecureStorage::get('secrets');
}
}
Copied!
// JavaScript
import { Biometric, SecureStorage, On, Off, Events } from '@nativephp/mobile';
 
const viewSecrets = async () => {
await Biometric.prompt().id('vault-unlock');
};
 
On(Events.Biometrics.Completed, async ({ success, id }) => {
if (id === 'vault-unlock' && success) {
const { value } = await SecureStorage.get('secrets');
secrets.value = value;
}
});

Your app now has a proper vault. Use it for everything that matters.

Stay secure. 🔒