React + Adal + Typescript + Pnp/sp

Dmitry Korolev
Sep 9, 2018 · 7 min read

In this post, I’m going to show how to implement Azure AD Authentication in SPA based on React & Typescript and call the SharePoint rest services through the @pnp/sp library. You can find many examples of how to use some of these technologies together, but I haven’t found any post, guide or examples that unites all of these technologies. At the same time, there are some hidden obstacles here. Therefore, perhaps, this post will save a lot of time for someone. Let’s go ahead!

Create a new App

First of all, I’m going to create a new react app. I use the create-react-app tool with react-script-ts script to speed up this process.

create-react-app demo-React-Adal-Typescript-pnp --scripts-version=react-scripts-ts

When the app is created, we’re ready to go forward.

Register Azure AD Application

The next step is to register the Azure AD Application. Once you’ve done, its key can be used to implement authentication in your app. In general, we should go through the following steps to register a new app:

  1. Go to the Microsoft Azure Portal -> Azure Active Directory -> App registrations and click on the “New application registration”.
  2. Enter the name of the application, select Application Type “Native” and enter the redirect url of the application (url, where you want to host the application).
  3. When the app is created we need to go to its manifest and set the property “oauth2AllowImplicitFlow” to true.
  4. Now we need to grant our application the necessary permissions to access the SharePoint Online. Go to the App Settings -> Required permissions -> Add -> Select API -> Office 365 SharePoint Online.
  5. In the “APPLICATION PERMISSIONS” section select the following items:

• “Read and write items and lists in all site collections”
• “Read and write items in all site collections”
• “Read items in all site collections”

6. Save changes.

Here you can find some additional information about registering Azure Active Directory apps.

Configure Azure AD Authentication

There are many implementations for the Azure AD Authentification on frontend and the most used is surely the angular-adal library. But since we’re going to use React, angular-adal isn’t really the best choice. Of course, we could use the simple adal.js, but if we don’t want to reinvent the wheel we should find and use something that more associated with react. React-adal is exactly what we need. So, let’s try to add this great library in our app and see how simple it is.

npm install react-adal

Actually, when I’ve just started using this lib, there were no TypeScript definitions and we couldn’t use it with TypeScript (at least it was very problematic). Therefore I decided to create them and add to the DefinitelyTyped.

DefinitelyTyped is the repository for high quality TypeScript type definitions.

My PR was reviewed and approved by responsible person. After that, definitions were added to the DefinitelyTyped repository and automatically published in npm. So, now you can use them. Since we use TypeScript we have to add these definitions to our project.

npm install --save-dev @types/react-adal

The next thing we’re going to do is add a config file (adalConfig.ts). Its code you can see below.

import { AdalConfig, AuthenticationContext } from 'react-adal';// Endpoint URL
export const endpoint = 'https://<tenant name>.sharepoint.com/';
// App Registration ID
const appId = '<your app registration ID>';
export const adalConfig: AdalConfig = {
cacheLocation: 'localStorage',
clientId: appId,
endpoints: {
api:endpoint
},
postLogoutRedirectUri: window.location.origin,
tenant: '<tenant name>.onmicrosoft.com'
};
export const authContext = new AuthenticationContext(adalConfig);

As you can see, this file contains the necessary settings to ensure authentication, like endpoint URL, azure ad app registration id, tenant, etc. And now we need to wrap the code in index.tsx with runWithAdal function from the react-adal lib to ensure Azure AD Authentication in our app.

import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;runWithAdal(authContext, () => {
ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
},DO_NOT_LOGIN);

And we’re ready to go. If we start the app with npm start command, we’ll be redirected to the authentication page.

Authentication page

When we log in, we’ll be able to see the default page.

Default page

Congrats, now our app supports Azure AD authentication and we’re ready to go to the second part that related to calling the SharePoint rest services.

Call the SharePoint rest services directly

To call the SharePoint rest services in each request we have to include the authorization token, that we receive after login. Otherwise we’ll get the 401 Unauthorized error. We should include this token in the Authorization header with the following format:Bearer <token>. But we don’t need to do this directly, because react-adal contains a wrapper, that automatically includes the current token in our request. So, let’s try to use it. First of all, we need to add the following code to our config file.

export const adalApiFetch = (url: string, options: any = {}) => {
return adalFetch(authContext, endpoint, fetch, url, options);
}

To make sure everything works as expected, I’m going to make some simple request to SharePoint, for example, we can request and show the title of the SharePoint root web. Let’s make some changes in the App component to implement this behavior.

import { adalApiFetch } from './adalConfig';interface IAppState {
webTitle: string;
}
class App extends React.Component<{}, IAppState> {
constructor(props: any) {
super(props);
this.state = { webTitle: '' };
}
public componentWillMount() {
const headers = {
"accept": "application/json;odata=verbose"
};
const spWebUrl = 'https://<tenant name>.sharepoint.com';
const url = `${spWebUrl}/_api/web/title`;
adalApiFetch(url, {headers})
.then(r => r.json())
.then(r => {
this.setState({webTitle: r.d.Title});
});
}
public render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.tsx</code> and
save to reload.
</p>
<h1>
SharePoint Web Title: {this.state.webTitle}
</h1>
</div>
);
}
}
export default App;

…we can see, that this works fine.

A little bit modified default page

But, actually, this isn’t а good idea to call the SharePoint REST services in this way, because we all know about the @pnp/sp package and all its advantages.

The SharePoint Patterns and Practices client side libraries were created to help enable developers to do their best work, without worrying about the exact REST api details. Built with feedback from the community they represent a way to simplify your day-to-day dev cycle while relying on tested and proven patterns.

Using @pnp/sp

First of all, we need to install the required packages.

npm install @pnp/logging @pnp/common @pnp/odata @pnp/sp --save

Now we’re ready to use this cool library. Let’s try to modify the componentWillMount method in the App component.

public componentWillMount() {
const spWebUrl = 'https://<tenant name>.sharepoint.com';
const web = new Web(spWebUrl);
web.select("Title").get().then(w => {
this.setState({
webTitle : w.Title
});
});
}

It looks a little bit better, isn’t it? But, unfortunately, if we try to run this code we’ll see the 403 Forbidden error. That’s to be expected. It happens because @pnp/sp doesn’t know anything about our authentication token, respectively this token wasn’t added to the headers of the request. Fortunately, @pnp/sp allows us to preset the headers, which will be applied to all requests. That’s exactly what we need! But before that, let’s make some changes in our config file (adalConfig.ts).

import { AdalConfig, adalGetToken, AuthenticationContext } from 'react-adal';// Endpoint URL
export const endpoint = 'https://<tenant name>.sharepoint.com/';
// App Registration ID
const appId = '<your app registration ID>';
export const adalConfig: AdalConfig = {
cacheLocation: 'localStorage',
clientId: appId,
endpoints: {
api:endpoint
},
postLogoutRedirectUri: window.location.origin,
tenant: 'wbengineering.onmicrosoft.com'
};
class AdalContext {
private authContext: AuthenticationContext;
constructor() {
this.authContext = new AuthenticationContext(adalConfig);
}
get AuthContext() {
return this.authContext;
}

public GetToken(): Promise<string | null> {
return adalGetToken(this.authContext, endpoint);
}
public LogOut() {
this.authContext.logOut();
}
}
const adalContext: AdalContext = new AdalContext();
export default adalContext;

As you may have noticed, now we export an instance of the AdalContext class. This class is a “layer” between the AuthenticationContext and other code. It encapsulates the AuthenticationContext and provide an interface (public methods GetToken , LogOut and getter for the AuthenticationContext) to work with it.

It’s time to finish, so the last thing I’m going to do is modify the index.tsx to set the headers (as mentioned previously, @pnp/sp will apply these headers to all requests).

import { sp } from '@pnp/sp';
import { runWithAdal } from 'react-adal';
import adalContext from './adalConfig';
const DO_NOT_LOGIN = false;runWithAdal(adalContext.AuthContext, () => {
adalContext.GetToken()
.then(token => {
sp.setup({
sp: {
headers: {
Authorization: `Bearer ${token}`
}
}
});
const rootDiv = document.getElementById('root') as HTMLElement;
ReactDOM.render(<App />, rootDiv);
registerServiceWorker();
});
}, DO_NOT_LOGIN);

That’s it! Our app works perfectly.

Default page. Final version

I should tell that there is another way to apply an authentication token with @pnp/sp. The trick is to set the custom client that will be used by @pnp/sp to make requests. In this case, we can manage all the subtleties of the request, including the headers. But in my opinion, the approach described above is more correct and simpler. Our goal is to apply the header, so we do exactly that and nothing extra.

Full code, you can find here. That’s all, thanks for reading!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade