HTTP CLI client with OAuth 2.0 authentication.
You know curl is powerful command line tool and you can make any complex HTTP request to every servers.
But the target web server is secured by OAuth 2.0, you must send another HTTP request to the authorization server
before making principal request. And more, you should to manage issued access tokens for every resources.
aurl is a command-line tool that process OAuth 2.0 dance and manage access/refresh tokens automatically.
Client credentials and tokens are stored in your OS keyring or 1Password, not in plain-text files.
Note: Currently, aurl is not support OAuth 1.0a. Your pull-request is appreciated.
This README describes aurl v2. v2 is not compatible with v1 on the command line and in the config file. See Migrating from v1.
brew tap classmethod/repos
brew install aurlIf you want to keep using v1, install aurl-v1 instead:
brew install aurl-v1aurl-v2-alpha conflicts with aurl. If you have it installed, switch with brew uninstall aurl-v2-alpha && brew install aurl.
Download the pre-compiled binaries from GitHub Releases.
Requires Go 1.26.10 or later (see go.mod).
git clone https://github.com/classmethod/aurl.git
cd aurl
make # or: go build -o bin/aurl
bin/aurl --version$ aurl add # register a profile interactively
Enter Profile Name: myapi
Select Grant Type:
...
$ aurl myapi https://api.example.com/users
...http.response.body...
$ aurl myapi /users # if base_url is set for the profile
...http.response.body...| command | behavior |
|---|---|
aurl <profile> <url|path> |
Send a request with the access token of <profile>. |
aurl <profile> |
Prompt for the URL. The prompt shows the profile's base_url if it is set. |
aurl |
Select a profile from a list, then prompt for the URL. |
An argument that does not start with http:// or https:// is treated as a path and appended to the profile's base_url
(simple concatenation, not RFC 3986 resolution: base_url = https://api.example.com/v1 + /users → https://api.example.com/v1/users).
A path without base_url is an error.
Interactive prompts need a terminal, so in scripts and CI always pass both: aurl <profile> <url>.
aurl sends the access token in the Authorization: Bearer header. When there is no valid token, aurl refreshes it with
the refresh token if one is available, otherwise it runs the grant flow of the profile. Tokens are saved in the keyring.
If the API returns 401, aurl exits with status 1 without printing the body and keeps the stored token; if the token was
revoked on the server side, use --renew-token.
By default aurl prints the response body to stdout. Errors are printed to stderr.
aurl honors the HTTPS_PROXY / HTTP_PROXY / NO_PROXY environment variables.
$ aurl myapi -X POST /users -d '{"name":"john"}'
...http.response.body...
$ aurl myapi --no-print-body --print-headers -X OPTIONS /users
{"Content-Type":["application/json;charset=UTF-8"],"Date":["Tue, 17 Feb 2015 08:16:41 GMT"], ...}aurl <profile> <url> is a shortcut for aurl curl <profile> <url>. aurl help curl shows the request flags.
| flag | description |
|---|---|
-X, --request=METHOD |
HTTP request method (default: GET) |
-H, --header=HEADER:VALUE |
Add an HTTP header. Repeatable. Overrides the profile's content_type / user_agent |
-d, --data=DATA |
HTTP request body |
-k, --insecure |
Disable SSL certificate verification |
--[no-]print-body |
Print the response body to stdout (default: enabled) |
--[no-]print-headers |
Print the response headers as JSON to stdout (default: disabled) |
--renew-token |
Ignore the stored token and run the grant flow again |
| flag | environment variable | description |
|---|---|---|
-v, --verbose |
Verbose logging to stderr (includes HTTP dumps, see below) | |
--version |
Show the version | |
--backend=BACKEND |
AURL_BACKEND |
Secret backend. See Secret backends |
--op-vault-id and other --op-* |
AURL_OP_* |
1Password settings |
-v output contains the access token, the client secret (Basic auth of token requests), the password of the password grant
and the token response as is, so be careful when sharing it or pasting it into an issue.
The v1 flags -p / --profile, -V (use --version) and -h (use --help) no longer exist.
aurl (profile selection) and aurl add (grant type selection) show a selection list:
| key | action |
|---|---|
| printable ASCII character | filter the list (case-insensitive substring match) |
| Backspace | delete the last filter character |
| ↑ / ↓, Ctrl-P / Ctrl-N | move the cursor |
| Enter | select |
| Ctrl-C / Ctrl-D | cancel |
When stdin or stderr is not a terminal (and on Windows), a numbered list is shown and you enter the number instead.
$ aurl addaurl add asks for the profile name, the grant type and the settings required for it, then writes the settings to
~/.aurl/config and the client ID / client secret (and username / password for the password grant) to the keyring.
Content type and user agent default to application/json and aurl when left empty.
If the profile already exists, aurl add asks whether to overwrite it (you re-enter every setting).
Adding a profile also removes its stored token.
There is no command to remove a profile. Delete its section from ~/.aurl/config and the keyring entries <profile> and
<profile>-TokenInfo by hand.
The following names are reserved for subcommands and cannot be used as profile names:
add, migrate, help, curl, exec, run, list, remove, clear, login, export, rotate, proxy, token
aurl add rejects them and aurl migrate skips them. If your config already has a profile with a reserved name,
call it with aurl curl <name> <url>.
Profile settings are stored in ~/.aurl/config (INI file).
Set AURL_CONFIG_FILE to use another path. The file is created when it does not exist.
Each section name is used as the profile name (case-sensitive). A section name containing . is a child section and
inherits missing keys from its parent (e.g. [myapi.staging] from [myapi]), so be careful when using . in profile names.
Create profiles with aurl add: the client ID / secret are stored in the keyring, so a profile written only in the config
file fails with "Failed to get credentials". You can edit the config afterwards (e.g. to add base_url).
| key name | description | default |
|---|---|---|
| grant_type | authorization_code, implicit, password or client_credentials |
(none, required) |
| auth_server_auth_endpoint | OAuth2 authorization endpoint URI | (none) |
| auth_server_token_endpoint | OAuth2 token endpoint URI | (none) |
| redirect | redirect URI | (none) |
| scopes | space separated scope values | (none) |
| content_type | Content-Type header sent with every request |
application/json |
| user_agent | User-Agent header |
aurl |
| base_url | base URL used when a path is given | (none) |
Required keys per grant type:
| grant_type | required keys | secrets used (keyring) |
|---|---|---|
authorization_code |
auth_server_auth_endpoint, auth_server_token_endpoint, redirect | client ID, client secret |
implicit |
auth_server_auth_endpoint, redirect | client ID |
password |
auth_server_token_endpoint | client ID, client secret, username, password |
client_credentials |
auth_server_token_endpoint | client ID, client secret |
For authorization_code, aurl opens the authorization URL in the browser and asks for the authorization code.
For implicit, aurl opens the authorization URL and asks you to paste the token response as JSON
(e.g. {"access_token":"...","token_type":"Bearer","expires_in":3600}).
[myapi]
grant_type = authorization_code
auth_server_auth_endpoint = https://auth.example.com/oauth/authorize
auth_server_token_endpoint = https://auth.example.com/oauth/token
redirect = https://auth.example.com/oauth/oob
scopes = read write
base_url = https://api.example.com
[batch]
grant_type = client_credentials
auth_server_token_endpoint = https://auth.example.com/oauth/token
scopes = read
content_type = application/x-www-form-urlencodedclient_id, client_secret, username and password are not read from the config file.
They are stored in the keyring under the profile name by aurl add or aurl migrate.
Access / refresh tokens are stored in the keyring under <profile>-TokenInfo.
Select the backend with --backend or AURL_BACKEND. The default is the OS keyring.
aurl --help lists the backends available on your platform.
| backend | description | required settings |
|---|---|---|
keychain |
macOS Keychain (default on macOS) | |
wincred |
Windows Credential Manager (default on Windows) | |
secret-service |
Secret Service, e.g. GNOME Keyring (default on Linux) | |
op |
1Password Service Account | --op-vault-id, AURL_OP_SERVICE_ACCOUNT_TOKEN (prompted if not set) |
op-connect |
1Password Connect server | --op-vault-id, --op-connect-host, AURL_OP_CONNECT_TOKEN (prompted if not set) |
op-desktop |
1Password desktop app integration | --op-vault-id, --op-desktop-account-id |
| flag | environment variable | description |
|---|---|---|
--op-vault-id |
AURL_OP_VAULT_ID |
UUID of the 1Password vault |
--op-connect-host |
AURL_OP_CONNECT_HOST |
1Password Connect server HTTP(S) URI |
--op-desktop-account-id |
AURL_OP_DESKTOP_ACCOUNT_ID |
1Password account name or UUID for the desktop app |
--op-timeout |
AURL_OP_TIMEOUT |
Timeout for 1Password API operations (default: 15s) |
1Password items are created with the title prefix aurl and the tag aurl.
To use op-desktop, enable "Integrate with other apps" in the Developer settings of the 1Password app.
op-desktop does not work with the pre-compiled Linux binaries because they are built without cgo.
$ export AURL_OP_VAULT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ export AURL_OP_DESKTOP_ACCOUNT_ID=my.1password.com
$ aurl --backend op-desktop myapi https://api.example.com/path/to/resource- Run
aurl migrate. It reads~/.aurl/profiles(or--profiles-file=PATH), writes the settings to~/.aurl/configand the secrets to the keyring.- v1 defaults are applied to missing keys (
grant_type = authorization_code,scopes = root, client ID / secretaurl), anddefault_content_type/default_user_agentare renamed. - Profiles that already exist in
~/.aurl/configare skipped. Useaurl migrate --forceto overwrite them. - Profiles with a reserved name are skipped. Rename them in
~/.aurl/profilesand re-run.
- v1 defaults are applied to missing keys (
- Check that your profiles work:
aurl <profile> <url>(the v1defaultprofile is nowaurl default <url>). Tokens are not migrated, so you have to authenticate again. ~/.aurl/profilesis left untouched. It contains plain-text secrets, so delete it once you have verified the migration.~/.aurl/token/*.jsonis not used by v2 and contains plain-text tokens, so you can delete it as well.
| Item | v1 | v2 |
|---|---|---|
| Command | aurl [-p profile] <url> (defaults to default) |
aurl <profile> <url> (profile required) |
| Config file | ~/.aurl/profiles |
~/.aurl/config |
| client_id / client_secret / username / password | Plain text in the config file | Keyring (aurl add) |
| Key names | default_content_type / default_user_agent |
content_type / user_agent |
| Default Content-Type | None | application/json |
| Default User-Agent | aurl-<version> |
aurl |
| Tokens | ~/.aurl/token/*.json (keychain in the alpha) |
Keyring (re-authentication required) |
- Fork (https://github.com/classmethod/aurl/fork)
- Create a feature branch named like
feature/something_awesome_featurefrom themasterbranch - Commit your changes
- Rebase your local changes against the
masterbranch - Run test suite with the
go test ./...command and confirm that it passes - Run
gofmt -s - Create new Pull Request against the
masterbranch
$ make deps
$ make