oauth2-cli
v1.1.4
Published
Acquire API access tokens via OAuth 2.0 / OpenID Connect within CLI tools
Readme
oauth2-cli
Acquire API access tokens via OAuth 2.0 / OpenID Connect within CLI tools
Install
npm i oauth2-cliUsage
import { Client, FileStorage } from 'oauth2-cli';
type ExpectedResponse = {
value: string;
};
const client = new Client({
name: 'Example API',
reason: 'an example script',
credentials: {
client_id: 'm3C6dGQJPJrvgwN97mTP4pWVH9smZGrr',
client_secret: '2XUktyxU2KQmQAoVHxQXNaHZ4G7XqJdP',
redirect_uri: 'http://localhost:3000/example/redirect',
authorization_endpoint: 'https://example.com/oauth2/auth',
token_endpoint: 'https://example.com/oauth2/token'
},
storage: new FileStorage('/path/to/token/file.json');
});
console.log(
client.fetchJSON<ExpectedResponse>('https://example.com/path/to/api/endpoint')
);Broadly speaking, having provided the configuration, the client is immediately ready to accept requests. If an stored access token is available, it will be used (and transparently refreshed as necessary, if possible). If no access token is available, the authorization flow will be triggered by the request, opening a browser window for the user to sign in and provide their authoriztion.
Instantiate a Client
credentials
A Client requires some minimal information in order to interact with an OAuth 2.0 authorized API. The OAuth 2.0 base credentials set is a client_id, client_secret, authorization_endpoint, token_endpoint, and a redirect_uri. For an OpenID-authenticated API, you could provide a client_id, client_secret, issuer, and redirect_uri and the Client will query the issuer for further details regarding required connection parameters (it is built on to of openid-client).
name and reason
It is strongly recommended that you provide a human-readable name for the client that will be used in user messages explaining what is being accessed (e.g. the name of the API or service) and a human-readable reason for the user to provide this access (e.g. the name of your app or script). Messages are structured in the manner:
...to authorize access to
nameforreason, do this...
storage
The refresh_token can be persisted by passing an implementation of Token.Storage, such as FileStorage which expects a path to a location to store a JSON file of access token data. There are more secure ways to store your tokens, such as @oauth2-cli/qui-cli's EnvironmentStorage which can be linked to a 1Password vault.
Registering localhost redirect URLs
redirect_uri to Localhost
Since the redirect_uri is receiving the authorization code in the Authorization Code token flow, the Client needs to be able to "catch" that redirect. The easy way to do this is to register a localhost address with the API (e.g. http://localhost:3000/my/redirect/path). When such a redirect URI is given to the client, it stands up (briefly) a local web server to receive that request at the port and path provided.
Not every API accepts a localhost redirect (it creates the possibility of CORS exploits that could lead to XSS vulnerabilities). For these APIs, using gcrtl or a similar system will work as well. (In the specific case of gcrtl, oauth2-cli will trim the leading /http/localhost:<port> from provided redirect_uri and expect the remainder of the path.)
http protocol
If you would prefer an https connection to localhost, you have to roll your own SSL certificate.
Request an endpoint
request()
As noted above, oauth2-cli is built on top of openid-client. The request() method is a pass-through to the openid-client fetchProtectedResource() function, with the configuration and accessToken managed by the Client.
class Client {
// ...
public async request(
url: requestish.URL.ish,
method = 'GET',
body?: requestish.Body.ish,
headers: requestish.Headers.ish = {},
dPoPOptions?: OpenIDClient.DPoPOptions
) {
// ...
}
}requestish.URL.ish are more forgiving types accepting not just those specific types, but reasonable facsimiles of them.
base_url and issuer for relative paths
If you would prefer to make requests to relative paths, rather than absolute paths, either configure a base_url or include an issuer in the credentials when instantiating the client. A base_url will preempt an issuer, if both are defined (handy for when the issuer is a different subdomain than the API endpoints).
requestJSON<J>()
Given that many APIs return JSON-formatted responses, it is convenient to just get that JSON (optionally pre-typed based on what you expect to receive) rather than having to process the response yourself.
class Client {
// ...
public async requestJSON<
J extends OpenIDClient.JsonValue = OpenIDClient.JsonValue
>(
url: requestish.URL.ish,
method = 'GET',
body?: requestish.Body.ish,
headers: requestish.Headers.ish = {},
dPoPOptions?: OpenIDClient.DPoPOptions
) {
// ...
}
}fetch() and fetchJSON<J>()
Aliases for request() and requestJSON<J>() that use Fetch API-style arguments.
