The Vibes — the unofficial Laracon US Day 3 event. Early Bird tickets available until March 31!
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. 🔒