Keep secrets - fakeivchenko/lwjwae GitHub Wiki
Tokens and passwords belong in the credential store of the system, which encrypts them for the user and keeps them from other users, and not in a file or a row of the store. In this guide, a mail application saves an API token, shows that it has one, and forgets it.

Save and read a secret
The secrets belong to the application by its name, so the application needs one:
public class Vault {
public static void main(String[] args) {
ApplicationParameters parameters = ApplicationParameters.builder().name("Mail").build();
try (Application application = Application.create(parameters)) {
Secrets secrets = application.secrets();
application.bind(
"saveToken",
String.class,
token -> {
secrets.set("api-token", token);
return null;
});
application.bind("savedToken", Void.class, _ -> secrets.get("api-token").orElse(null));
application.bind("forgetToken", Void.class, _ -> secrets.delete("api-token"));
application.show(
WindowParameters.builder()
.title("Mail")
.size(new WindowSize(560, 380))
.resource("secrets/index.html")
.build());
application.run();
}
}
}
| Method | What it does |
|---|---|
set(key, value) |
Saves value under key, in place of the value that was there. |
get(key) |
Returns the value, or an empty Optional when there is none. |
delete(key) |
Deletes the value, and returns whether there was one. |
A value takes at most Secrets.MAXIMUM_SIZE, 2560 bytes in UTF-8: the limit of the Credential
Manager of Windows, kept on every platform, so that what works on one works on all. A longer value
or a blank key throws IllegalArgumentException. If the system refuses, for example because the
user didn't unlock the store, the call throws SecretStoreFailedException.
Keep the secret in Java
The page asks Java whether a token is saved, and shows only its end:
const show = (token) => {
document.querySelector("#status").textContent = token === null
? "No token is saved."
: `A token is saved: ${"•".repeat(8)}${token.slice(-4)}`;
};
window.savedToken().then(show);
document.querySelector("#save").addEventListener("submit", async (event) => {
event.preventDefault();
const token = document.querySelector("#token").value;
await window.saveToken(token);
document.querySelector("#token").value = "";
show(await window.savedToken());
});
There is no lwjwae.secrets on the page, on purpose: a secret that the page can read is a secret
that every script on the page can read. Bind what the page needs, such as a call that signs a
request with the token, and keep the token itself in Java.
Platforms
| Platform | Where the secret goes |
|---|---|
| Windows | A generic credential of the Credential Manager, named APPLICATION/KEY, such as Mail/api-token. |
| macOS | A generic password of the login keychain, with the application as the service and the key as the account. |
| Linux | A password of the Secret Service, such as GNOME Keyring or KWallet, through libsecret, with the application and the key as its attributes. |
Good to know
- A call blocks until the system answers, and the system can first ask the user to unlock the store. A handler of a binding runs on a virtual thread of its own, so it can call it; on the UI thread, don't.
- On Linux, the desktop needs a running Secret Service. GNOME and KDE Plasma start one with the session; a minimal window manager might not.
- On Windows,
cmdkey /list:Mail*lists the secrets of the application, and Credential Manager in the Control Panel shows them under Windows Credentials.