DEV Community

Serge Artishev
Serge Artishev

Posted on

Empowering Your Azure Projects with a Secure CLI: A Step-by-Step Guide

In this easy-to-follow blog post, we're going to guide you step by step on how to build a command-line interface (CLI) application using oclif, the NodeJs Open CLI Framework. This application will be your trusted tool for securely logging into your Azure account.

What makes it even cooler? Just like other popular CLI tools such as az, our application securely stores your authenticated token in local storage. This means you can effortlessly use it to execute other CLI commands without having to log in every time.

Let's dive in and get started!

Prerequisites

Before we begin, make sure to install the oclif globally by using the following command:

npm install -g oclif
Enter fullscreen mode Exit fullscreen mode

Step 1: Setting Up Your Project

Initiate your CLI project with the following command:

oclif generate your-cli-app-name
Enter fullscreen mode Exit fullscreen mode

Navigate to your project folder and install necessary packages for Azure identity and HTTP client axios:

npm install @azure/identity axios
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating the Login Command

Generate your login command using the following:

oclif generate command login
Enter fullscreen mode Exit fullscreen mode

This will create a template file for your login command. Open src/commands/login.ts and implement the login functionality using the DefaultAzureCredential which attempts to use multiple authentication methods based upon the available environment variables:

import { Command } from '@oclif/core';
import { DefaultAzureCredential } from '@azure/identity';

export default class Login extends Command {
  static description = 'Login to Azure account';

  async run() {
    const credential = new DefaultAzureCredential();

    try {
      const token = await credential.getToken('https://management.azure.com/.default');
      if (token) {
        // Storing the token securely will be implemented in the next steps
        this.log('Login successful.');
      }
    } catch (error: any) {
      this.error(`Login failed: ${error.message}`, { exit: 1 });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Integrating Keytar for Robust and Secure Token Storage

In this step, we're going to enhance our application's security by integrating Keytar, a node module that leverages the operating system's secure services for storing sensitive information. This prevents any unauthorized access to the tokens, safeguarding your user credentials effectively.

First off, let's install Keytar to manage the secure storage of the token acquired during the login process:

npm install keytar
Enter fullscreen mode Exit fullscreen mode

Next, we modify the login command to utilize Keytar. This will allow us to store the authentication token in a safe and encrypted manner, a strategy commonly adopted to prevent token theft and ensure overall application security. Here is how you can do it:

import * as keytar from 'keytar';
// ... (rest of the imports)

// ... (within the try block in the run method)
if (token) {
  await keytar.setPassword('your-cli-app-name', 'azureToken', token.token);
  this.log('Login successful.');
}
// ...
Enter fullscreen mode Exit fullscreen mode

By implementing Keytar, not only are we fortifying our application's security, but we are also creating a seamless user experience. Keytar facilitates automatic and secure retrievals of tokens, which means users won't have to re-enter their credentials every time they run a command, thus aligning with best practices observed in well-established CLI tools such as az.

Remember, a secure application is a trusted application. Let's move forward, ensuring the safety of our user's data with the reliable functionalities provided by Keytar!

Step 4: Creating the Logout Command

Generate your logout command:

oclif generate command logout
Enter fullscreen mode Exit fullscreen mode

Now, populate the src/commands/logout.ts with the following content to enable logging out functionality:

import { Command } from '@oclif/core';
import * as keytar from 'keytar';

export default class Logout extends Command {
  static description = 'Logout from Azure account';

  async run() {
    await keytar.deletePassword('your-cli-app-name', 'azureToken');
    this.log('Logout successful.');
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Creating the Account Show Command with Stored Access Token

As we venture further, we'll be exploring how to utilize the stored access token in crafting our custom CLI commands. Let's start with something simple yet functional, like an 'account show' command that fetches and displays the username of the connected Azure account.

First, initiate the process by generating the account show command. Use the following command to do this:

oclif generate command account:show
Enter fullscreen mode Exit fullscreen mode

Before we populate the newly created command file, we need to craft a utility function that will fetch the username of the logged-in Azure account. This utility function will leverage the stored access token we saved during the login process.

But before we proceed, ensure to install axios - a promise-based HTTP client for the browser and Node.js:

npm install axios
Enter fullscreen mode Exit fullscreen mode

Next, we'll create a utility module to encapsulate the logic to fetch the logged-in username. Create a new file at src/utils/azureUtils.ts and insert the following content:

import axios from 'axios';
import * as keytar from 'keytar';

export async function getLoggedInUserName(): Promise<string> {
  try {
    // Retrieve the stored token
    const token = await keytar.getPassword('your-cli-app-name', 'azureToken');
    if (!token) throw new Error('No token found');

    // Use the token to fetch the user information from Azure
    const response = await axios.get('https://management.azure.com/me?api-version=2020-10-01', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });

    // Return the user principal name or a default string if it's not available
    return response.data.userPrincipalName || 'Unknown User';
  } catch (error: any) {
    throw new Error(`Could not fetch user info: ${error.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

With our utility function ready, let’s now create a command that makes use of this function to display the Azure account details on the terminal. Open src/commands/account/show.ts and implement the command as follows:

import { Command } from '@oclif/core';
import { getLoggedInUserName } from '../../utils/azureUtils';

export default class AccountShow extends Command {
  static description = 'Show the connected Azure account username';

  async run() {
    try {
      // Fetch the username using the utility function
      const username = await getLoggedInUserName();
      // Log the username to the terminal
      this.log('Connected account username:', username);
    } catch (error: any) {
      // Handle any errors that occur during the process
      this.error(`Error fetching username: ${error.message}`, { exit: 1 });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This section now elucidates the usage of the stored access token and how it integrates seamlessly in creating a new CLI command. Let me know if there is anything else you'd like to add or modify!

Step 6: Understanding Scopes in Azure

When using the DefaultAzureCredential class to authenticate, it is essential to understand scopes. Scopes define the resources and permissions your token will have access to in the Azure ecosystem. Different Azure resources will require different scopes. Adjust the scopes as per your needs. For example:

const token = await credential.getToken('https://management.azure.com/.default');
Enter fullscreen mode Exit fullscreen mode

Here, we are requesting a token with the scope of managing resources on Azure. Make sure to select the appropriate scope for your application needs.

Step 7: Testing Your Commands

Test your CLI application using the following commands:

./bin/dev login
./bin/dev logout
./bin/dev account show
Enter fullscreen mode Exit fullscreen mode

You should see the expected outputs for each command if everything is set up correctly.

Absolutely! Here's how you can extend the guide to include building, publishing, and using the CLI application:


Step 8: Building the Final CLI Application

After thoroughly testing your commands, it's time to compile your CLI application into a version that's ready for production. Here's how you can do this:

npm run build
Enter fullscreen mode Exit fullscreen mode

This command should bundle your application and prepare it for distribution. Make sure to check for any errors and fix them before proceeding to the next step.

Step 9: Publishing Your CLI Application

Now that you have built your CLI application, the next step is to publish it so that it can be easily installed and used by others. Here’s how you can publish your application to the npm registry:

  1. Ensure that you are logged into your npm account by running:
   npm login
Enter fullscreen mode Exit fullscreen mode
  1. Publish your package using:
   npm publish
Enter fullscreen mode Exit fullscreen mode

This command will upload your package to the npm registry, making it available for others to install and use.

Step 10: Sample Usage

Your CLI application is now live! Here's how users can install it and a few sample commands they can use to get started:

  1. To install the application, users should run:
   npm install -g your-cli-app-name
Enter fullscreen mode Exit fullscreen mode
  1. Once installed, they can start using the application with commands such as:
   your-cli-app-name login
   your-cli-app-name logout
   your-cli-app-name account show
Enter fullscreen mode Exit fullscreen mode

Remember to provide a comprehensive README with your package, detailing all available commands and usage examples to assist users in navigating through your application efficiently.

Conclusion

Congratulations on building a secure CLI application with oclif, integrated with Azure authentication, featuring secure login, logout, and account detail retrieval functionalities. Keep exploring the power of CLI tools with oclif!

Top comments (0)