Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,21 @@ Examples:
# Request and validate a JWT token
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL

# Request a JWT token, write it to a file and refresh it before expiration
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --token-output-file /tmp/token --refresh

Usage:
https-wrench jwtinfo [flags]

Flags:
-h, --help help for jwtinfo
--refresh Run in foreground and automatically refresh the token
--renew-threshold float Token renewal threshold as a percentage of lifetime (default 80)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
--request-url string HTTP address to use for the JWT token request
--request-values-file string File containing the JSON encoded values to use for the JWT token request
--request-values-json string JSON encoded values to use for the JWT token request
--token-file string File containing the JWT token
--token-output-file string File where the acquired/refreshed token will be written
--validation-url string Url of the JSON Web Key Set (JWKS) to use for validating the JWT token

Global Flags:
Expand All @@ -258,6 +264,18 @@ Decode a token from a file:
❯ https-wrench jwtinfo --token-file mytoken.jwt
```

Request a token and save it to a file:

```shell
❯ https-wrench jwtinfo --request-url https://auth.example.com/token --request-values-json '{"client_id":"foo"}' --token-output-file ./token.jwt
```

Request a token, save it to a file, and keep it refreshed until interrupted:

```shell
❯ https-wrench jwtinfo --request-url https://auth.example.com/token --request-values-json '{"client_id":"foo"}' --token-output-file ./token.jwt --refresh --renew-threshold 90
```

### HTTPS Wrench jwks

`jwks` generates a public JSON Web Key Set from a PEM-encoded public key. This is useful for exposing your public keys at a `.well-known/jwks.json` endpoint.
Expand Down
86 changes: 80 additions & 6 deletions internal/cmd/jwtinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ Copyright © 2026 Zeno Belli <xeno@os76.xyz>
package cmd

import (
"context"
"io"
"net/http"
"os"
"os/signal"
"syscall"

"github.com/MicahParks/keyfunc/v3"
"github.com/spf13/cobra"
Expand All @@ -19,11 +23,17 @@ var (
flagNameRequestURL = "request-url"
flagNameTokenFile = "token-file"
flagNameJwksURL = "validation-url"
flagNameRefresh = "refresh"
flagNameTokenOutputFile = "token-output-file"
flagNameRenewThreshold = "renew-threshold"
requestJSONValues string
requestValuesFile string
requestURL string
tokenFile string
jwksURL string
refresh bool
tokenOutputFile string
renewThreshold float64
keyfuncDefOverride keyfunc.Override
)

Expand All @@ -48,11 +58,16 @@ Examples:

# Request and validate a JWT token
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL

# Request a JWT token, write it to a file and refresh it before expiration
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --token-output-file /tmp/token --refresh
`,
Run: func(cmd *cobra.Command, _ []string) {
var (
err error
tokenData jwtinfo.JwtTokenData
err error
tokenData *jwtinfo.JwtTokenData
client = &http.Client{}
requestValuesMap = make(map[string]string)
)

// TODO: remove global --config option
Expand All @@ -69,9 +84,6 @@ Examples:
}

if requestURL != "" {
client := &http.Client{}
requestValuesMap := make(map[string]string)

if requestValuesFile != "" {
requestValuesMap, err = jwtinfo.ReadRequestValuesFile(
requestValuesFile,
Expand Down Expand Up @@ -115,7 +127,7 @@ Examples:
}
}

if tokenData.AccessTokenRaw != "" {
if tokenData != nil && tokenData.AccessTokenRaw != "" {
err = tokenData.DecodeBase64()
if err != nil {
cmd.Printf("DecodeBase64 error: %s\n", err)
Expand All @@ -135,6 +147,47 @@ Examples:
cmd.Printf("error while printing token data: %s\n", err)
return
}

if tokenOutputFile != "" {
tokenData.WriteTokenToFile(tokenOutputFile, cmd.OutOrStdout())
}

if refresh {
if requestURL == "" {
cmd.Printf("Error: --refresh requires --request-url\n")
return
}

// Setup graceful shutdown
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)

go func() {
<-sigCh
cancel()
}()

cmd.Printf("Starting refresh loop...\n")

err := tokenData.RefreshLoop(
ctx,
requestURL,
requestValuesMap,
client,
io.ReadAll,
renewThreshold,
tokenOutputFile,
cmd.OutOrStdout(),
)
if err != nil {
cmd.Printf("Refresh loop exited with error: %s\n", err)
} else {
cmd.Printf("Refresh loop stopped gracefully.\n")
}
}
} else {
_ = cmd.Help()
}
Expand Down Expand Up @@ -179,6 +232,27 @@ func init() {
"Url of the JSON Web Key Set (JWKS) to use for validating the JWT token",
)

jwtinfoCmd.Flags().BoolVar(
&refresh,
flagNameRefresh,
false,
"Run in foreground and automatically refresh the token",
)

jwtinfoCmd.Flags().StringVar(
&tokenOutputFile,
flagNameTokenOutputFile,
"",
"File to write the refreshed token to",
)

jwtinfoCmd.Flags().Float64Var(
&renewThreshold,
flagNameRenewThreshold,
80.0,
"Percentage of token lifetime to wait before refreshing",
)

// Either read a token from a file or request it from an HTTP address
jwtinfoCmd.MarkFlagsMutuallyExclusive(flagNameTokenFile, flagNameRequestURL)
jwtinfoCmd.MarkFlagsOneRequired(flagNameTokenFile, flagNameRequestURL)
Expand Down
Loading
Loading