Exploration: Showing Dynamics CRM Data In Vue.JS (SPA)

Today, I learned how to use MSAL.js 2.0 to do authentication (login) and authorization (checking if the person can access the target resource) to display Dynamics CRM Accounts data in Vue.js. The idea is we want to create a SPA (Single Page Application) that using Vue.js. The user must be login first and then can fetch the Accounts data in the table format. Without further ado, here are the steps:

Create App Registration

Login to portal.azure.com using your Dynamics CRM Administrator account > Open Azure Active Directory > App registrations blade > New registration button. Here are my settings:

Register an application

Next, we need to go to the API permissions blade > Add a permission button > choose Dynamics CRM > select user_impersonation > click Add permissions button.

Add permission

Click the “Grant admin consent..” button to give consent:

Grant admin consent

Once you click that button, then the status will be changed as follow:

Consent granted

With this, our App registration finished. We only need to go to the Overview page and copy the Application (client) ID.

Create Vue.JS

Here I will create Vue.js version 3 and combine it with TypeScript. For step by step to create the project (because it is so long), you can follow this blog post.

After you finish creating the project, what you need to do is run this command:

npm install @azure/msal-browser

Edit your store/index.ts like below:

import Vue from 'vue';
import Vuex from 'vuex';
import { AuthenticationResult } from '@azure/msal-browser';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isLogin: false,
    idTokenExpiresOn: new Date(0),
    preferredName: '',
    accessTokenExpiresOn: new Date(0),
    accessToken: '',
    homeAccountId: '',
  },
  mutations: {
    doLogin(state, item: AuthenticationResult) {
      if (!item.idToken) return;

      state.isLogin = true;
      state.idTokenExpiresOn = item.expiresOn || new Date(0);
      state.preferredName = item.account?.username || '';
      state.homeAccountId = item.account?.homeAccountId || '';
    },
    getAccessToken(state, item: AuthenticationResult) {
      if (!item.accessToken) return;

      state.accessToken = item.accessToken;
      state.accessTokenExpiresOn = item.expiresOn || new Date(0);
    },
    doLogOut(state) {
      state.isLogin = false;
      state.preferredName = '';
      state.accessToken = '';
      state.accessTokenExpiresOn = new Date(0);
      state.idTokenExpiresOn = new Date(0);
    },
  },
});

Create a new component named Login.vue and here is the code:

<template>
  <div>
    <div v-if="getIsLogin()">
      <h1>Welcome {{ getUserName() }}</h1>
      <button @click="doLogOut()">Logout</button>
    </div>
    <button v-if="!getIsLogin()" @click="doLogin()">Login</button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import {
  Configuration,
  AuthenticationResult,
  PublicClientApplication,
} from "@azure/msal-browser";
import store from "@/store";

@Component
export default class Login extends Vue {
  getIsLogin(): boolean {
    return store.state.isLogin;
  }

  getUserName(): string {
    return store.state.preferredName;
  }

  getMsalConfig(): Configuration {
    const msalConfig: Configuration = {
      auth: {
        clientId: "your_application_client_id",
        redirectUri: "http://localhost:8080/",
        authority:
          "https://login.microsoftonline.com/your_organization_name",
      },
      cache: {
        cacheLocation: "sessionStorage",
        storeAuthStateInCookie: true,
      },
    };

    return msalConfig;
  }

  doLogin(): void {
    if (store.state.isLogin) return;

    const config = this.getMsalConfig();
    const userAgentApp = new PublicClientApplication(config);
    userAgentApp
      .loginPopup({
        scopes: ["user.read"],
        prompt: "select_account",
      })
      .then((value: AuthenticationResult) => {
        store.commit("doLogin", value);
        const account = userAgentApp.getAccountByHomeId(
          store.state.homeAccountId
        );
        return userAgentApp.acquireTokenSilent({
          scopes: ["https://your_crm_org_url.crm5.dynamics.com/.default"],
          account: account || undefined,
        });
      })
      .then((value: AuthenticationResult) => {
        store.commit("getAccessToken", value);
      });
  }

  doLogOut(): void {
    const config = this.getMsalConfig();
    const userAgentApp = new PublicClientApplication(config);
    userAgentApp.logoutPopup({
      mainWindowRedirectUri: "http://localhost:8080/",
      account: userAgentApp.getAccountByHomeId(store.state.homeAccountId),
    });
  }
}
</script>

You need to replace your_application_client_id with your clientId of yours, your_organization_name with your Azure AD Domain Name (you can find more about it here), and your_crm_org_url with your CRM URL. Because this is just a demo, I put all the config in the file but you MUST NOT follow this method and put the important things as secure as you can.

After the user successfully login, we will call acquireTokenSilent to get the accessToken. This accessToken is a string value that will be used to check whether the user is valid to access the resource or not.

Create a new component named Account.vue and here is the code:

<template>
  <div>
    <h2>Account Table</h2>
    <button @click="getAccounts()">Fetch Accounts</button>
    <br />
    <br />
    <table style="padding: 20px">
      <thead>
        <tr>
          <td>Fullname</td>
          <td>Website URL</td>
        </tr>
      </thead>
      <tbody v-if="getAccountExists()">
        <tr v-for="account in accounts" :key="account.AccountId">
          <td>{{ account.name }}</td>
          <td>{{ account.websiteurl }}</td>
        </tr>
      </tbody>
      <tbody v-if="!getAccountExists()">
        <tr>
          <td colspan="2">No Data Fetch..</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts">
import store from "@/store";
import { Component, Vue } from "vue-property-decorator";

interface Account {
  accountid: string;
  name: string;
  websiteurl: string;
}

@Component
export default class Login extends Vue {
  accounts: Account[] = [];

  getAccountExists(): boolean {
    return this.accounts.length > 0;
  }

  getAccounts(): void {
    if (!store.state.accessToken) return;

    fetch(
      "https://your_crm_org_url.crm5.dynamics.com/api/data/v9.1/accounts?$select=name,websiteurl&$top=10",
      {
        method: "get",
        headers: new Headers({
          Accept: "application/json",
          "OData-MaxVersion": "4.0",
          "OData-Version": "4.0",
          Authorization: "Bearer " + store.state.accessToken,
        }),
      }
    )
      .then((fetchValue: Response) => {
        return fetchValue.json() as Promise<{ value: Account[] }>;
      })
      .then((data: { value: Account[] }) => {
        this.accounts = data.value;

        console.log(this.accounts);
      });
  }
}
</script>

<style scoped lang="scss">
table,
th,
td {
  border: 1px solid black;
}

th,
td {
  padding: 10px;
}
</style>

This code is pretty simple, like what you already know, we just need to do HttpRequest with the GET action to the WebAPI that we want to call (account), then we need to set the accessToken to the Authentication header.

The last part is we need to modify the Home.vue component that will show the Login and Account component:

<template>
  <div class="home">
    <Login></Login>
    <Account></Account>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import Login from "@/components/Login.vue";
import Account from "@/components/Account.vue";

@Component({
  components: {
    Login,
    Account,
  },
})
export default class Home extends Vue {}
</script>

Demonstration

Demo SPA

You can check the code in this repository.

One thought on “Exploration: Showing Dynamics CRM Data In Vue.JS (SPA)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.