In my previous article in CODE-Magazine, I talked about why I'm super-excited about AngularJS2, and what benefits it brings. To recap, you get better performance, cleaner code, TypeScript, it's standards-based, and the fact that it's a lot easier to learn. Certainly, over time, best practices will emerge and we'll push this platform to its limits as well. But there are two things with every new platform:
- When it's fresh and new, we're excited about the possibilities and capabilities.
- When it's fresh and new, we miss all the support and libraries and knowledgebase that has yet to be created for it.
Speaking of libraries and knowledgebases, for AngularJS1, we had Ionic, we had Electron, and we had some pretty sweet TDD frameworks. I hope to cover their replacements in future articles. In this article, I'll talk about an empty hole that most Microsoft developers will notice, and that is ADAL.js.
ADAL.js
ADAL.js is the JavaScript active directory authentication library. Although it could be used outside of AngularJS, it was written with AngularJS1 in mind. It worked on a concept of pushing in HTTP Interceptors that examined every request going out and coming in. Every request going out was automatically modified by adding an access token. Every request coming in automatically checked for a 401 (Unauthorized) HTTP status code, and asked the user to perform the Azure AD authentication. ADAL.js also renewed access tokens using a hidden IFrame, and had enough smarts to understand the limitations of various browsers and provide suitable workarounds.
ADAL.js could be improved however.
- I wish it didn't create global variables such as “Username”.
- I wish it were written with Electron and Cordova in mind and supported concepts such as refresh tokens.
- I wish it provided hooks allowing saving refresh tokens over multiple runs of the application, just like ADAL.js's other ADAL libraries provided for other platforms.
Still, I was happy to have ADAL.js because writing all that authentication logic can be cumbersome. Well, for AngularJS2, there isn't such a library. What's more, AngularJS2 requires us to think of the application structure for authentication somewhat differently, which I intend to show in this article.
What About OAuth
A lot of us want to target simple OAuth with grant_type:password
or something simple. At the end of the day, no matter how you authenticate, you're handed an access token. Using that access token, you can make successful requests. Now, how you get that access token may differ. A simple grant_type:password
- say supported by your ADFS 3.0 endpoints, or perhaps Google etc. - is a simple CORS POST
request. On the other hand, AzureAD authentication uses a two-step process to authenticate:
- It redirects you to the AzureAD authentication end point with enough information in the querystring parameters allowing AzureAD to identify your application, and a redirect URI, i.e., where to send the
ID_TOKEN
back once the authentication has been performed. - Once your application receives an
ID_TOKEN
, it does another GET request, asking for an access token for specific resources. In other words,ID_TOKEN
identifies you as the user, but then to get an access token that lets you call Microsoft Graph, you have to request an access token specific to the Microsoft Graph, etc.
This two-step process, while necessary for AzureAD, Common Consent, and Office 365, is arguably both more powerful and more complex than simple OAuth. I use the word “complex” with some reservations though - two GET requests is hardly complex, but you get my drift.
In order to create an application structure, I've encapsulated all this logic into its own Angular2 service. If you wish to replace it with simple OAuth, you just need a different, albeit simpler, implementation of this service. The rest of the concepts demonstrated here will work with simple OAuth also.
What Will I Build
In this article, I'm going to build a simple application. It will use AngularJS2; specifically, it'll make use of Routing, HTTP, and demonstrate how you can call APIs protected by AzureAD using AngularJS2. I'll also show how I structured the application, the naming conventions I used, etc. There's a lot of detail to cover here; I'll keep this article pertinent to AngularJS2, and refer you to ancillary concepts, such as registering applications in AzureAD, or the specific authentication flow to use (and more) to alternate resources on the Internet. Also, I'll share source code.
Here are the references you'll need to follow along with this article.
- Azure Active Directory basics: http://www.codemag.com/Article/1503041
- Extending Office365: http://www.codemag.com/Article/1507031
- Source code for this article: https://github.com/maliksahil/Angular2AzureAD
Basic Application Structure
This application is built using AngularJS2 and TypeScript. My dev environment is VSCode. To start cloning this git, repo https://github.com/maliksahil/Angular2Start
. This gives you a basic Angular2 app using TypeScript. You can open that directory in VSCode, and run npm install and npm start to get started. If you're curious, feel free to see how that app is constructed; it's a plain vanilla Angular2 app that's almost exactly like the quick starter you can find at angular.io
.
Now let's start building the actual application logic. What I intend to do is:
- Set up basic routing between two main routes: files and login. The intention is that the starter page consists of files, and it prompts the user to click a button, which, behind the scenes, executes a command to query for files in the Microsoft Graph. And if the user isn't logged in, it redirects the user to the login route, where the user clicks a button to perform AzureAD authentication and gain an access token. The goal here is that the author of the application logic shouldn't have to worry about the cruft of authentication. I should be able to call the REST API's, authentication - no matter what kind it is - and should be transparent to the application logic.
- Create a service to help with AzureAD authentication.
- Create another service to encapsulate all my HTTP calls that I wish to be authenticated.
- Create a file to hold some constants that I'll need for the AzureAD app identification, etc.
Let's get started. For the routing, you'll need the central app.component.ts
, so go ahead and create a file with that name under the app folder.
You'll need two pages, login and files. Go ahead and create a login.component.ts
and login.component.html
. Similarly, create a files.component.ts
and a files.component.html
. Note the naming convention I'm following; by looking at the filename, it's pretty clear what each file does. Also, it's a lot easier to search for files in Visual Studio code if they are named intuitively.
Next, let's add files for the services. Add a file called azureADAuthHelper.service.ts
, which is the service that helps you encapsulate AzureAD authentication. Add another file called authenticatedHttp.service.ts
, which encapsulates all HTTP calls that I wish to be authenticated.
Finally, add a file called serviceConstants.ts
. This holds the various constants you need to identify the application with AzureAD.
Your final file structure, minus the .gitignore
, .js.map
, .js
, and node_modules
files, should look like Figure 1.
Now, let's start authoring some code.
Registering the App in Azure AD and serviceConstants.ts
Go ahead and register your app as a native client application in your Office365 tenancy. Also, remember to set OAuth2 implicit flow. Ensure that the permissions are set up as shown in Figure 2. If what I just said makes zero sense to you, please read the articles referenced in the “What Will I Build” section.
Once you get a client ID, update your serviceConstants.ts
with the code shown in Listing 1. Note that your client ID will be different from mine - and don't try using mine; I've already deleted it.
Listing 1: The serviceConstants.ts file
export class SvcConsts {
public static CLIENT_ID: string =
"5c74c564-8ea2-477d-acdd-09618455bd1d";
public static TENTANT_ID: string =
"winsmartsdev.onmicrosoft.com";
public static GRAPH_RESOURCE: string =
"https://graph.microsoft.com";
}
Writing the Services
Next, let's focus on the services. The first service is the AzureADAuthHelper.ts
file. The logic here is quite easy. It exposes a public method called login
that starts the entire login process. The login logic works as follows:
- It redirects you to the AzureAD sign-in page with some information that includes the
ClientID
andRedirectURI
.ClientID
is the identity you put in theserviceConstants.ts
file andRedirectURI
is where you wish the ID Token to be sent back to, which, in this case, is the app itself. - Once you get the ID Token, do another redirect to the AzureAD token endpoint with this ID token, Client ID, TenantID, and requesting permission on a specific resource. Assuming that the app has that access granted, which you set up in Figure 2, you're returned an access token. This access token is then available as a public property on this object.
You can find the entire code in the associated code download at https://github.com/maliksahil/Angular2AzureAD, but really, it's the above logic you need to know. The code, which is quite wordy, does the above described logic.
Once you've written the AzureADAuthHelper.ts
, focus on the authenticatedHttp.service.ts
service. The intention of this service is to act as an authenticated HTTP client. It's a class that's injectable, and it inherits from the normal HTTP client. This AuthenticatedHttpService
, accepts the azureADAuthHelper
class as an injected dependency. The reason that I've structured the code in this form is because now I can inject any other kind of authentication helper, for instance, an ADFS 3.0 OAuth authentication helper.
The AuthenticatedHttpService
has a constructor that accepts the following dependencies:
- HTTP (from
angular2/http
) - Router (from
angular2/router
) - AzureADAuthHelper (from
azureADAuthHelper.ts
)
You might be wondering, why I'm injecting the router. The idea is that this authenticatedHttpService
client wraps the normal, out-of-the-box HTTP
object; if your call results in a 401 error (unauthorized), it automatically redirects to the /Login
route. This means that the rest of the application need not do all these checks; I have them neatly tied up in a single class.
Next, I need to implement the various GET, POST, DELETE, PUT methods that implement this behavior. For the sake of brevity, I'm only demonstrating a simple GET call, but you can implement other methods, as necessary. The logic for the GET method can be seen in Listing 2.
Listing 2: The AuthenticatedHttpService get method
get(url) {
var promise = new Promise((resolve, reject) => {
let headers = new Headers();
this.createAuthorizationHeader(headers);
var observable = this.http.get(url, { headers: headers });
observable.subscribe(
res => {
resolve(res.json());
},
err => {
if (err.status == 401) {
this._router.navigate(['/Login']);
} else reject(err);
});
});
return promise;
}
As can be seen in Listing 2, if the return happens to be an error, and the error happens to be a 401 (unauthorized), I'm simply routing to the Login page.
The Login Component
The login component now becomes incredibly simple! Really, all it needs to do is offer a sign in button that calls AzureADAuthHelper.login()
. The TypeScript portion of this component can be seen in Listing 3.
Listing 3: login.component.ts
import { Component } from "angular2/core";
import { AzureADAuthHelper } from "./AzureADAuthHelper.service";
@Component({
selector: "login",
templateUrl: "app/login.component.html",
providers:[AzureADAuthHelper]
})
export class LoginComponent {
constructor(private _azureADAuthHelper:AzureADAuthHelper) {}
login() {
this._azureADAuthHelper.login();
}
}
Also, the HTML for this component in the login.component.html
file can be seen in this snippet:
<button (click)="login()">Sign-in</button>
As can be seen, its just a simple button that calls AzureADAuthHelper - which is injected into the constructor as a dependency.
The Functionality (Files) Component
Now here is where you can begin to reap the benefits of the application structure. The code for the TypeScript component of the Files component can be seen in Listing 4.
Listing 4: files.component.ts
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import { AuthenticatedHttpService }
from './authenticatedHttp.service';
import { AzureADAuthHelper } from "./AzureADAuthHelper.service"
@Component({
selector: "files",
templateUrl: "app/files.component.html",
providers: [
HTTP_PROVIDERS,
AuthenticatedHttpService,
AzureADAuthHelper]
})
export class FilesComponent {
private files = [];
constructor(private _http: AuthenticatedHttpService) { }
getFiles() {
var resourceURL =
"https://graph.microsoft.com/v1.0/me/drive/root/children";
this._http.get(resourceURL).then((files) => {
this.files = files.value;
})
}
}
As is evident, you're injecting the authentication services. Then, instead of using the usual out?of–the-box HTTP client to make the REST call, you're using the HTTP client to make the call. Yep, it's really as simple as that now. What's happening behind the scenes is that the services are encapsulating the redirect to login, etc., the junk that honestly, I'd rather not have to worry about when I'm writing the actual business logic.
As my application grows, I just need to tell my team that they should use the AuthenticatedHttpService
client instead of the normal HTTP client. And since the API is protected by authentication anyway, I think there'll be no danger of someone not using the unauthenticated client; it simply won't work. Plus, if I ever wish to use a different authentication provider, the change is encapsulated in a single file.
As you can guess, if the call succeeds, I'm setting the files
property on my exported
class, which I'm simply databinding to the view, as can be seen in Listing 5.
Listing 5: The databound view
<button (click)="getFiles()">Get Files</button>
<ul>
<li *ngFor="#file of files">
<span>{{file.name}}</span>
<span>{{file.size}}</span>
</li>
</ul>
The Main App and Routing
All that's left to do now is set up the overall app routing and bootstrap code.
The bootstrap code can be seen here:
import {bootstrap}
from 'angular2/platform/browser';
import {AppComponent}
from './app.component'
import {
Router, RouteConfig,
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS
} from "angular2/router";
bootstrap(AppComponent, [ROUTER_PROVIDERS]);
It's the usual Angular2 bootstrap code that you'd expect. I'm injecting router-related directives and providers because I intend to use them all over the application. You could inject the authentication helper class here as well.
The app.component.ts
, which is your bootstrapped component, simply needs to define the routing now. This can be seen in Listing 6. As can be seen, you're defining two routes: one for login and one for the application itself. As the app grows more complex, you can define more routes here, or define child routes in underlying components. As long as you continue to use the Authentication helper services, it should all still work.
Listing 6: The app.component.ts defining the overall routes
import { Component } from 'angular2/core';
import { Router, RouteConfig, ROUTER_DIRECTIVES,
ROUTER_PROVIDERS}
from "angular2/router";
import {LoginComponent} from './login.component';
import {FilesComponent} from './files.component';
@Component({
selector: 'app',
template: '<router-outlet></router-outlet>',
directives: [ROUTER_DIRECTIVES],
providers: [ROUTER_PROVIDERS]
})
@RouteConfig([
{ path: '/files',
name: 'Files',
component: FilesComponent },
{ path: '/login',
name: 'Login',
component: LoginComponent }
])
export class AppComponent {
constructor(private _router: Router) {
this._router.navigate(['/Files']);
}
}
Running the Application
From the terminal or command prompt, run npm start to launch the application and typescript compilation. The browser should launch pointing to http://localhost:3000
. Ensure that http://localhost:3000/*
is specified as a wildcard redirect URI in your AzureAD app registration. The application should load at the /files
URL because that is defined as the first URL in the RouteConfig
. You could also set as the default URL if you wish. The application should look Figure 3.
When you click on Get Files, the application tries to do an HTTP call to the server, but is met with a 401 error because you haven't authenticated yet. The application should now show you a Login button, as shown in Figure 4, because you have redirected to the login page.
Clicking on the Sign-in button takes you to AzureAD, whereupon providing the correct credentials results in two redirects. Watch closely, because they happen so fast, you might miss them, or just run your code in debug mode and set breakpoints.
After those directs, you'll have an access token, and you'll be back on the files component, similar to Figure 3. Because you have a valid access token now, clicking on Get Files now queries your OneDrive for the files you might have there, as shown in Figure 5.
That's all there is to it. You just wrote a simple AngularJS2 application that's able to query OneDrive. You could easily enhance this to add more functionality if you wish.
Summary
This article showed you a practical implementation of how you can write AngularJS2 single page applications that authenticate with AzureAD, and are able to call AzureAD protected APIs, such as Microsoft Graph. In the process of writing this app, I was able to demonstrate how I composed my application, how I structured and named the files. I also showed how I made use of dependency injection, a key feature of AngularJS, to encapsulate all of my authentication logic in a single place, making the rest of my application neat and clean.
This is by far not production-ready code. Ideally, I'd want to enhance the authentication helper classes to allow me to store the ID token for long-running applications. Or be able to support multiple authentication providers. Or perhaps the ability to package this app into a mobile app using Cordova, or desktop apps using Electron. That and more will be covered in subsequent articles.
In the meanwhile, happy coding!