Storing API Keys Safely in a Tauri App — Don't Just Use LocalStorage

작성자

카테고리:

← 피드로
DEV Community · hiyoyo · 2026-06-10 개발(SW)
Cover image for Storing API Keys Safely in a Tauri App — Don't Just Use LocalStorage

hiyoyo

All tests run on an 8-year-old MacBook Air. All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.

Users enter their Gemini API key into your app. Where does it go? The wrong answer is localStorage. The right answer is the system keychain.

Why localStorage Is Wrong

localStorage in a Tauri WebView is just a file on disk. On macOS:

~/Library/WebKit/com.yourapp.app/WebsiteData/LocalStorage/

Enter fullscreen mode Exit fullscreen mode

It’s not encrypted. It’s not protected by the keychain. Any process running as the same user can read it.

For an API key that costs the user money if leaked, this is not acceptable.

The Right Approach: System Keychain

For straightforward API key storage, use the OS keychain directly via the keyring crate:

# Cargo.toml
[dependencies]
keyring = "2"

Enter fullscreen mode Exit fullscreen mode

use keyring::Entry;

pub fn save_api_key(service: &str, key: &str) -> Result<(), AppError> {
    let entry = Entry::new(service, "api_key")?;
    entry.set_password(key)?;
    Ok(())
}

pub fn get_api_key(service: &str) -> Result<String, AppError> {
    let entry = Entry::new(service, "api_key")?;
    Ok(entry.get_password()?)
}

pub fn delete_api_key(service: &str) -> Result<(), AppError> {
    let entry = Entry::new(service, "api_key")?;
    entry.delete_password()?;
    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

On macOS, keyring stores in Keychain Access. The key is protected by the OS. Other processes can’t read it without explicit user permission.

NOTE: tauri-plugin-stronghold はより高度な暗号化ストレージが必要な場合の選択肢です。API キー1つを安全に保存するだけなら keyring で十分です。

The Tauri Command Layer

#[tauri::command]
async fn save_api_key_cmd(key: String) -> Result<(), AppError> {
    save_api_key("com.yourapp.gemini", &key)
}

#[tauri::command]
async fn get_api_key_cmd() -> Result<String, AppError> {
    get_api_key("com.yourapp.gemini")
}

Enter fullscreen mode Exit fullscreen mode

The frontend never stores the key. It calls invoke('get_api_key_cmd') when it needs it. The key lives in the keychain, not in JavaScript memory or localStorage.

First-Launch UX

On first launch, get_api_key returns an error (key not found). Use this to show an onboarding screen — not an error dialog.

const apiKey = await invoke<string>('get_api_key_cmd').catch(() => null)

if (!apiKey) {
    // Show setup flow, not an error
    setScreen('onboarding')
} else {
    setScreen('main')
}

Enter fullscreen mode Exit fullscreen mode

There’s a meaningful difference between “something went wrong” and “welcome, let’s get you set up.” The key-not-found state is expected on first launch — treat it that way.

The Verdict

API key storage is a trust signal. Users who see “stored securely in macOS Keychain” feel better about entering their key than users who don’t know where it goes.

Two hours to implement properly. Worth it for every app that handles user credentials.

If this was useful, a ❤️ helps more than you’d think!

👉 HiyokoBar → https://hiyokomtp.lemonsqueezy.com/checkout/buy/f9b85321-6878-40aa-a472-ff748d6de2d5

X → @hiyoyok

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다