At Altostra, we strive to provide our customers with the most time-saving products with minimum disturbance. One of the ways we achieve that is by providing the same user experience across all user interfaces.
It's important to us to enable our customers to use any identity provider they choose, whether a username and password, Google account, GitHub account, or any other. As avid supporters of Everything as a Service, we decided to let Auth0 do the heavy lifting of identity management and security.
For web applications, there are many possible solutions, such as an embedded login form, a custom login form, and the Auth0 Lock. But what about the CLI?
The common solution: using secrets
One way to approach this challenge is to generate a set of secrets for the user to put in some file.
The problem with this approach:
- The secrets don't rotate
- The user has to do this manually
- The user has to do this for each machine
- Users can't log-out easily; they need to manually delete their secrets
Although it's a common solution, it doesn't fit with Altostra's vision of user-ergonomics. Instead, we decided to implement another solution that enables users to log in and out without generating secrets.
How we did it
We started, as always, by reading documentation and the Auth0 blog. From there, it was straightforward what to do to obtain an authentication token for the user:
- Start an HTTP server on the
localhostaddress with a custom port
- Open a parameterized Auth0 authorization URL in a browser, which presents the user with a familiar authentication interface
- Handle a redirect request from the Auth0 login page back to our
localhostsocket and obtain the authentication code provided in the query string
- Call the Auth0 Management API to obtain an authentication token for the user
Once we have the token, we can store it in a file, accessible only to the user, just as we would store secrets. Later, any of our locally running apps can use this token to authenticate.
How this addresses the problems mentioned above
- We can enforce expiration on the token and have the user re-authenticate, thus rotating credentials periodically
- The login process is simple and doesn't take much time, and it can be as simple as a single click
- Logging-in this way on any other machine takes very little time and is no longer an issue
- We provide the user with a log-out command that deletes the stored token
Step 1: Start a local HTTP server
We start an HTTP server on the
localhost address with a custom port, for example,
4242. We also implement a fallback mechanism in case this port is already in use. We try to use the next one or the next one after that. There's no reason to throw errors at the user if we can try 50 ports in sequence before we finally give up.
Additionally, when we start the server, we also start a timer that stops the server after a predefined interval. We want to stop the server in case the user abandons the authentication process.
Step 2: Open an Auth0 authentication URL
Using data from our Auth0 account and application, we construct an authentication URL:
https://domain.auth0.com/authorize ?response_type=code &code_challenge_method=S256 &code_challenge=<code-challenge> &client_id=<client-id> &redirect_uri=http://localhost:11111 &scope=<token-scope> &audience=<token-audience> &state=<state>
The parameters are:
domain.auth0.comis the Auth0 domain. It's located in the settings of the application in the Auth0 management console
response_typemust be set to
code_challenge_methodshould be set to
S256. The other possible value is
clean, but that would be a bad idea, security-wise
code_challengemust be a string generated for each request
client_idis the Auth0 application Client ID. It's located in the settings of the application in the Auth0 management console
redirect_uriis the URI to which the request with an authorization code should redirect. We must set it to the
localhostaddress with the same port number that we used to start the HTTP server
stateis an "opaque" value used as a CSRF-token to prevent CSRF attacks. It is strongly advised to use this value
In addition to these arguments, we can optionally pass
audience values to use in the authentication request
Step 3: Handle the response
After the user completes the authentication in the browser, which either succeeds or fails, the login page redirects the user to the URL we provide in
redirect_uri. The query string of the URL contains either a
code value, when successful or an
error_description values when unsuccessful.
It's important to keep in mind that when we respond to the redirected request, we must return an HTML response. Since we can't close the browser window, or tab, without user interaction, it is vital to present a friendly message and guide the user to close the window or provide a button to do so. The same goes for a failed login response.
Step 4: Get the authentication token
Using the code we obtained in step 3, we call the Auth0 Management API to obtain an authentication token for our user. We provide the Management API with the
code_verifier and the authentication
code values. The request looks like this:
URL : https://domain.auth0.com/oauth/token Method : POST Headers : ContentType: application/x-www-form-urlencoded Body : grant_type=authorization_code&client_id=<client-id>&code_verifier=<code-verifier>&code=<autnentication-code>&redirect_uri=<uri>
grant_typemust be set to
authorization_code, indicating that we're also passing the
client_idis required and must be the Auth0 application Client ID
code_verifieris required and must be the same
code_verifiervalue generated at the beginning of the process
codeis required and must be the authentication code we got from the login page redirect
redirect_uriis required and must be the same as the
redirect_urivalue used in the authorization URL
Next Steps: Use the authentication token
Now that we have the authentication token, we store it in a file accessible only by the user, and then we use it to authenticate to our services.
Where's the Code?
We published the above process as an open-source library. You can find the source code at https://github.com/altostra/altostra-cli-login-auth0, which is also an example for this blog post. Feel free to use it and contribute to it. It is available as an NPM package here: https://www.npmjs.com/package/@altostra/cli-login-auth0
The library can use optimizations and improvements. We also have many more exciting technical challenges to conquer, and we're looking for talented engineers to join us. If you want to help build the future of cloud computing, please visit our Careers page.