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 localhost address 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 localhost socket 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

Implementation

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.com is the Auth0 domain. It's located in the settings of the application in the Auth0 management console
  • response_type must be set to code
  • code_challenge_method should be set to S256. The other possible value is clean, but that would be a bad idea, security-wise
  • code_challenge must be a string generated for each request
  • client_id is the Auth0 application Client ID. It's located in the settings of the application in the Auth0 management console
  • redirect_uri is the URI to which the request with an authorization code should redirect. We must set it to the localhost address with the same port number that we used to start the HTTP server
  • state is 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 scope and 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 and 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_type must be set to authorization_code, indicating that we're also passing the authentication-code parameter
  • client_id is required and must be the Auth0 application Client ID
  • code_verifier is required and must be the same code_verifier value generated at the beginning of the process
  • code is required and must be the authentication code we got from the login page redirect
  • redirect_uri is required and must be the same as the redirect_uri value 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

What's next

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.

References