Skip to content
wly-pPublic

About

Desktop app for fetching and inspecting auth tokens across environments and APIs. Config-driven requests, JWT decoding with live expiry, OS-keychain password storage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Login Tool

CI

A small desktop app for grabbing auth tokens out of your APIs — across every environment, without hand-editing requests or pasting JWTs into a decoder.

Signed in, showing every token returned by one login

Why

If you test or develop against an authenticated API, the same chore keeps coming back: fire a login request at dev, then beta, then prod; dig the token out of the response; copy it; paste Bearer in front of it; drop it into Postman or curl. When it expires, do it again. When a second API has a different login shape, keep a second snippet around for that too.

Login Tool collapses that into one window. Environments are described in a config file (or through the UI), accounts are stored per environment, and one click gives you every token the response contained — decoded, with a live expiry countdown and one-click copy in the shape you actually need.

Features

  • Any auth API. Method, headers, and request body are configuration, not code. JSON, form-urlencoded, and raw bodies are supported, with {{variable}} substitution.
  • Every token at once. One login lists all returned tokens — access, refresh, id, session, whatever your API sends — each with its own expiry badge.
  • Reads tokens anywhere. From a nested JSON path, an explicit name-to-path mapping, a plain-text body, or response headers like Set-Cookie.
  • Copy how you need it. Raw token, Bearer <token>, or a full Authorization: Bearer <token> header.
  • JWTs decoded in place. Header and payload, plus a countdown that ticks down to expiry. Signatures are never verified — this is a viewer, not a validator.
  • Passwords encrypted at rest via the OS keychain.
  • Manual, single-shot logins. Nothing retries, nothing polls in the background.
  • Follows the system light/dark theme.

Install

macOS

Download the .dmg for your architecture from the latest release, open it, and drag Login Tool into Applications.

Chip File
Apple silicon logintool-<version>-mac-arm64.dmg
Intel logintool-<version>-mac-x64.dmg

Or install from the terminal:

curl -fsSL https://raw.githubusercontent.com/wly-p/logintool/main/scripts/install.sh | sh

That script picks the right architecture, downloads the matching build, and moves it into /Applications. If piping a script into a shell makes you uneasy — fair — read it first; it is about sixty lines.

From source

Works on macOS, Windows, and Linux. Requires Node 20 or newer.

git clone https://github.com/wly-p/logintool.git
cd logintool
npm install
npm start          # build and open the window
npm run dist       # or build installers into release/

Prebuilt Windows and Linux downloads are not published yet; build from source in the meantime.

macOS: allowing an unsigned app

Builds are not signed with an Apple Developer ID, so macOS will refuse to open the app on first launch when it has been quarantined. This is expected, and you should only work around it if you trust the source.

Right-click the app in Applications and choose Open, then confirm. Or go to System Settings → Privacy & Security and click Open Anyway next to the blocked-app notice. You only need to do this once.

Files fetched with curl are not quarantined, so the install script above will not trigger that prompt. Convenient, but worth knowing: it means Gatekeeper never checked the app.

Quick start

The repo ships with a mock auth server, so you can see the whole thing working without pointing it at anything real:

mkdir -p .local-config
cp examples/mock.config.yaml .local-config/config.yaml

npm run mock                                          # terminal 1
LOGINTOOL_CONFIG_DIR="$(pwd)/.local-config" npm start # terminal 2

Add an account with any username and password, then hit Log in. The six environments each demonstrate a different response shape. Passwords wrong, boom, slow, garbage, and empty trigger the corresponding failure modes.

Configuration

Two files live side by side, split by whether they are safe to share:

File Contents Share it?
config.yaml Environments and request specs Yes — no secrets
accounts.yaml Credentials, passwords encrypted Never

Their location, in priority order:

  1. LOGINTOOL_CONFIG_DIR, if set
  2. otherwise the per-platform data directory:
Platform Path
macOS ~/Library/Application Support/logintool/
Windows %APPDATA%\logintool\
Linux ~/.config/logintool/

The status bar at the bottom of the window shows the active directory; click it to reveal it in your file manager, or use Edit config.yaml to open the file directly. Reload re-reads both files without restarting.

Point LOGINTOOL_CONFIG_DIR at a folder in your team's repo to share one config.yaml between everyone, while each person keeps their own accounts.yaml locally.

config.yaml

version: 1

# Shared request shape. Every environment inherits it and can override any part.
defaults:
  request:
    method: POST
    headers:
      Content-Type: application/json
    body:
      type: json
      template: '{"username": "{{username}}", "password": "{{password}}"}'
  response:
    tokens: result

environments:
  # Same API in several environments: only the url differs.
  - name: dev
    request:
      url: https://auth-dev.example.com/user/login

  - name: prod
    request:
      url: https://auth.example.com/user/login

  # A different API: override the parts that differ.
  - name: keycloak
    request:
      url: https://sso.example.com/realms/demo/protocol/openid-connect/token
      headers:
        Content-Type: application/x-www-form-urlencoded
      body:
        type: form
        fields:
          grant_type: password
          client_id: "{{client_id}}"
          username: "{{username}}"
          password: "{{password}}"
    response:
      tokens:
        access: access_token
        refresh: refresh_token

Merge rules. Headers merge key by key, so an environment can add one header without restating the others. body and response.tokens are replaced wholesale, so switching to a different API shape is unambiguous. Merge semantics cannot express removing an inherited header — only overriding or adding one.

request

Field Notes
url Required. Supports {{variables}}.
method Defaults to POST.
headers Mapping. Values support {{variables}}.
body.type json, form, or raw. Omit body for no body.
body.template For json and raw: the body as a string.
body.fields For form: a mapping, url-encoded on send.

Variables available in any template: {{username}}, {{password}}, and every key in the account's extra map. A missing variable is an error raised before the request leaves the app — a literal {{password}} never reaches the network. For json bodies, substituted values are escaped, so a password containing " or \ still produces valid JSON.

response

Field Notes
format json (default) or text.
tokens For json: a dot path, or a { name: path } mapping. For text: the name to give the whole body.
headers Optional { name: headerName } — tokens read from response headers.

With a dot path, every scalar field under that object becomes a token, so a login that starts returning an extra token shows it without a config change. An empty path reads the top level of the response.

accounts.yaml

Managed from the UI; you rarely need to touch it.

dev:
  - id: 6f1c4b2a-...
    name: admin
    username: admin@example.com
    password: "enc:v1:BASE64..."
    extra:
      client_id: my-client

Everything under extra becomes a {{variable}} in the request templates — that is how OAuth flows get their client_id, scope, or tenant without any API-specific code.

Editing in the app

Environments and accounts can both be managed without opening a file. Use the + at the end of the tab strip to add an environment, or Edit environment for the current one.

The environment editor

Values inherited from the shared defaults block are tagged, and saving writes back only what actually differs — the defaults block stays intact rather than being copied into every environment.

Note that saving from the UI rewrites config.yaml with js-yaml, which does not preserve comments. Keep reference notes in examples/ rather than in your live config.

The account editor

Security

  • Passwords are encrypted with Electron's safeStorage, backed by the OS keychain (Keychain on macOS, DPAPI on Windows, libsecret on Linux). They are stored as enc:v1:<base64>.
  • If no keychain is available, the app says so in a banner and falls back to plain text rather than silently pretending to be secure.
  • The encryption key is tied to the application, so an accounts.yaml copied from another machine cannot be decrypted. Those accounts are flagged for re-entry instead of corrupting the file.
  • Because macOS builds are unsigned, updating the app may re-prompt for keychain access. Unsigned builds carry an ad-hoc signature whose hash changes every build, and macOS ties keychain permission to that signature. Expect a "Login Tool wants to use your confidential information" dialog after an update; allow it and stored passwords keep working. Deny it and the affected accounts show as password unreadable until you retype the password. A proper Developer ID signature would make this go away — it is on the roadmap, not done.
  • Passwords never leave the main process. The UI receives a "has password" flag, never the value.
  • accounts.yaml is not meant to be committed. The repo's .gitignore already excludes it.
  • JWTs are decoded for display only. Signatures are never verified.

Development

npm start        # build and run
npm run watch    # rebuild on save (run `npx electron .` separately)
npm run typecheck
npm test         # unit and integration tests
npm run mock     # mock auth server on 127.0.0.1:8787
npm run screenshot  # regenerate docs/img (needs the mock server running)
src/
  main/               main process (Node)
    main.ts             window and IPC
    preload.ts          contextBridge surface
    api.ts              executes one login from an environment spec
    config/             config.yaml + accounts.yaml, validation, encryption
    request/            template substitution, request building, token extraction
  renderer/           UI (browser context)
scripts/
  mock-server.mjs     dependency-free mock auth server
  screenshot.cjs      regenerates the README screenshots
  install.sh          macOS installer used by the curl one-liner
examples/             ready-to-use configuration samples

The request/ and config/ modules are pure functions with no Electron imports, which is what the test suite covers; api.test.ts runs against the mock server.

License

MIT — see LICENSE.

About

Desktop app for fetching and inspecting auth tokens across environments and APIs. Config-driven requests, JWT decoding with live expiry, OS-keychain password storage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages