diff --git a/01-Login/TUTORIAL.md b/01-Login/TUTORIAL.md new file mode 100644 index 00000000..3e053699 --- /dev/null +++ b/01-Login/TUTORIAL.md @@ -0,0 +1,428 @@ +# Tutorial + +This tutorial will guide you in modifying an Angular application demonstrating how users can log in and log out with Auth0 and view profile information. It will also show you how to protect routes from unauthenticated users. + + +> This tutorial assumes that you're able to use the [Angular CLI](https://cli.angular.io/) to generate new components and services on your project. If not, please place the files where appropriate and ensure that any references to them regarding import paths are correct for your scenario. + +## Create an Angular Application + +If you do not already have your own application, a new one can be generated using the [Angular CLI](https://cli.angular.io/). + +Install the CLI: + +```bash +npm install -g @angular/cli +``` + +Then generate a new Angular application by running the following command from your filesystem wherever you'd like your application to be created: + +```bash +ng new auth0-angular-demo +``` + +When asked _"Would you like to use Angular routing?"_, select **y** (yes). + +When prompted _"Which stylesheet format would you like to use?"_, select **CSS**. + +Once the project has been created, open the `auth0-angular-demo` folder in your favorite code editor. + +By default, the Angular CLI serves your app on port `4200`. This should run on port `3000` so that it matches the Auth0 URLs configured above. Open the `package.json` file and modify the `start` script to: + +```json +"start": "ng serve --port 3000" +``` + +> If you are following this tutorial using your own Angular application that runs on a different port, you should modify the URLs above when configuring Auth0 so that they match the host and port number of your own application. + +## Install the SDK + +While in the project folder, install the SDK using `npm` in the terminal (or use one of the other methods from the section above): + +```bash +npm install @auth0/auth0-spa-js --save +``` + +## Add the Authentication Service + +To manage authentication with Auth0 throughout the application, create an authentication service file and then copy the following code. This ensures that authentication logic is consolidated in one place and can be injected easily. Methods for interoperating between Angular with RxJS and the Auth0 SPA SDK are provided for you in the service. + +Use the CLI to generate a new service called `AuthService`: + +```bash +ng generate service auth +``` + +Open the `src/app/auth.service.ts` file inside your code editor and copy the following content: + +:::note +Make sure that the domain and client ID values are correct for the application that you want to connect with. +::: + +```ts +import { Injectable } from '@angular/core'; +import createAuth0Client from '@auth0/auth0-spa-js'; +import Auth0Client from '@auth0/auth0-spa-js/dist/typings/Auth0Client'; +import { + from, + of, + Observable, + BehaviorSubject, + combineLatest, + throwError, +} from 'rxjs'; +import { tap, catchError, concatMap, shareReplay } from 'rxjs/operators'; +import { Router } from '@angular/router'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + // Create an observable of Auth0 instance of client + auth0Client$ = (from( + createAuth0Client({ + domain: 'YOUR_AUTH0_DOMAIN', + client_id: 'YOUR_AUTH0_CLIENT_ID', + redirect_uri: `${window.location.origin}`, + }) + ) as Observable).pipe( + shareReplay(1), // Every subscription receives the same shared value + catchError((err) => throwError(err)) + ); + // Define observables for SDK methods that return promises by default + // For each Auth0 SDK method, first ensure the client instance is ready + // concatMap: Using the client instance, call SDK method; SDK returns a promise + // from: Convert that resulting promise into an observable + isAuthenticated$ = this.auth0Client$.pipe( + concatMap((client: Auth0Client) => from(client.isAuthenticated())), + tap((res) => (this.loggedIn = res)) + ); + handleRedirectCallback$ = this.auth0Client$.pipe( + concatMap((client: Auth0Client) => from(client.handleRedirectCallback())) + ); + // Create subject and public observable of user profile data + private userProfileSubject$ = new BehaviorSubject(null); + userProfile$ = this.userProfileSubject$.asObservable(); + // Create a local property for login status + loggedIn: boolean = null; + + constructor(private router: Router) { + // On initial load, check authentication state with authorization server + // Set up local auth streams if user is already authenticated + this.localAuthSetup(); + // Handle redirect from Auth0 login + this.handleAuthCallback(); + } + + // When calling, options can be passed if desired + // https://auth0.github.io/auth0-spa-js/classes/auth0client.html#getuser + getUser$(options?): Observable { + return this.auth0Client$.pipe( + concatMap((client: Auth0Client) => from(client.getUser(options))), + tap((user) => this.userProfileSubject$.next(user)) + ); + } + + private localAuthSetup() { + // This should only be called on app initialization + // Set up local authentication streams + const checkAuth$ = this.isAuthenticated$.pipe( + concatMap((loggedIn: boolean) => { + if (loggedIn) { + // If authenticated, get user and set in app + // NOTE: you could pass options here if needed + return this.getUser$(); + } + // If not authenticated, return stream that emits 'false' + return of(loggedIn); + }) + ); + checkAuth$.subscribe(); + } + + login(redirectPath: string = '/') { + // A desired redirect path can be passed to login method + // (e.g., from a route guard) + // Ensure Auth0 client instance exists + this.auth0Client$.subscribe((client: Auth0Client) => { + // Call method to log in + client.loginWithRedirect({ + redirect_uri: window.location.origin, + appState: { target: redirectPath }, + }); + }); + } + + private handleAuthCallback() { + // Call when app reloads after user logs in with Auth0 + const params = window.location.search; + if (params.includes('code=') && params.includes('state=')) { + let targetRoute: string; // Path to redirect to after login processsed + const authComplete$ = this.handleRedirectCallback$.pipe( + // Have client, now call method to handle auth callback redirect + tap((cbRes) => { + // Get and set target redirect route from callback results + targetRoute = + cbRes.appState && cbRes.appState.target + ? cbRes.appState.target + : '/'; + }), + concatMap(() => { + // Redirect callback complete; get user and login status + return combineLatest([this.getUser$(), this.isAuthenticated$]); + }) + ); + // Subscribe to authentication completion observable + // Response will be an array of user and login status + authComplete$.subscribe(([user, loggedIn]) => { + // Redirect to target route after callback processing + this.router.navigate([targetRoute]); + }); + } + } + + logout() { + // Ensure Auth0 client instance exists + this.auth0Client$.subscribe((client: Auth0Client) => { + // Call method to log out + client.logout({ + client_id: 'YOUR_AUTH0_CLIENT_ID', + returnTo: window.location.origin, + }); + }); + } +} +``` + +This service provides the properties and methods necessary to manage authentication across the application. An `auth0Client$` observable is defined that returns the same instance whenever a new subscription is created. The [Auth0 SDK for Single Page Applications](https://auth0.com/docs/libraries/auth0-spa-js) returns promises by default, so observables are then created for each SDK method so we can use [reactive programming (RxJS)](https://rxjs.dev/) with authentication in our Angular app. + +Note that the `redirect_uri` property is configured to indicate where Auth0 should redirect to once authentication is complete. For login to work, this URL must be specified as an **Allowed Callback URL** in your [application settings](${manage_url}/#/applications/${account.clientId}/settings). + +The service provides some basic methods, such as: + +- `getUser$(options)` - Requests user data from the SDK and accepts an options parameter, then makes the user profile data available in a local RxJS stream +- `login()` - Log in with Auth0 +- `logout()` - Log out of Auth0 + +In a Single Page Application, when the user reloads the page anything stored in app memory is cleared. We don't want the application to force the user to log in again if they did not log out, and still have an active session with the authorization server. + +We also [should not store sensitive session data in browser storage due to lack of security](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage). + +The `localAuthSetup()` method uses the `auth0-spa-js` SDK to check if the user is still logged in with the authorization server. If they are, their authentication state is restored in the front end when they return to the app after refreshing or leaving, without having to log in again. We can call this method from the `constructor` since our authentication service is a singleton. + +### Restoring Login State with Social Providers + +Users who are logged in with **username/password** will be silently reauthenticated automatically when the application reloads. No further action is needed for this type of login. + +If you are using the [classic Universal Login experience](https://auth0.com/docs/universal-login/classic) and would like users to authenticate using **[social identity providers](https://auth0.com/docs/connections#social)** (such as Google, Apple, Facebook, etc.), then you will need to configure those connections in your [Auth0 Dashboard](https://manage.auth0.com/dashboard). + +In the navigation menu, choose **Connections** - **Social**, and select the social connection you’d like to support. In the connection’s settings, click “How to obtain a Client ID?“ and follow the instructions to set up your own ID and secret. + +If you are using the [new Universal Login experience](https://auth0.com/docs/universal-login/new), the default enabled social connections will silently reauthenticate without additional configuration. However, you should still set up your own keys and avoid using [default Auth0 development keys](https://auth0.com/docs/connections/social/devkeys#limitations-of-developer-keys) in a production app. + +We also need to handle login redirects when the application loads. In the authentication service, `handleAuthCallback()` does several things: + +- Checks to see if the necessary query parameters (`code` and `state`) are present, and if they are: +- Stores the application route to redirect back to after login processing is complete +- Calls the JS SDK's `handleRedirectCallback` method +- Gets and sets the user's profile data +- Updates application login state +- After the callback has been processed, redirects the user to their intended route + +We can also call `handleAuthCallback()` from the `constructor`. + +> **Angular and the Auth0 SPA JS SDK:** `auth0-spa-js` is a promise-based library built using async/await, providing an agnostic approach for the highest volume of JavaScript apps. The Angular platform manages asynchronous code by [using reactive programming and observable streams with RxJS](https://angular.io/guide/rx-library). To enable the async/await library to work seamlessly with Angular’s stream-based approach, we have converted the async/await functionality to observables for you in this service. This improves the developer experience for interoperability between the SDK and the Angular platform. + +## Create a Navigation Bar Component + +If you do not already have a logical place to house login and logout buttons within your application, create a new component to represent a navigation bar that can also hold some UI to log in and log out: + +```bash +ng generate component nav-bar +``` + +Open the `src/app/nav-bar/nav-bar.component.ts` file and replace its contents with the following: + +```js +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../auth.service'; + +@Component({ + selector: 'app-navbar', + templateUrl: './nav-bar.component.html', + styleUrls: ['./nav-bar.component.css'] +}) +export class NavBarComponent implements OnInit { + + constructor(public auth: AuthService) { } + + ngOnInit() { + } + +} +``` + +The `AuthService` class you created in the previous section is being provided in the component in the constructor. It is `public` to enable use of its methods in the component _template_. + +Next, configure the UI for the `navbar` component by opening the `src/app/nav-bar/nav-bar.component.html` file and replacing its contents with the following: + +```html +
+ + +
+``` + +Methods and properties from the authentication service are used to log in and log out, as well as show the appropriate button based on the user's current authentication state. + +To display this component, open the `src/app/app.component.html` file and replace the default UI with the following: + +```html + +``` + +> **Checkpoint**: Run the application now using `npm start`. The login button should be visible, and you should be able to click it to be redirected to Auth0 to authenticate. After logging in with Auth0, the application should handle the callback appropriately and send you back to the default route. The logout button should now be visible. Click the logout button to verify that it works. + +## Show Profile Information + +Create a new component called "Profile" using the Angular CLI: + +```bash +ng generate component profile +``` + +Open `src/app/profile/profile.component.ts` and replace its contents with the following: + +```js +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../auth.service'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.css'] +}) +export class ProfileComponent implements OnInit { + + constructor(public auth: AuthService) { } + + ngOnInit() { + } + +} +``` + +All we need to do here is publicly inject the `AuthService` so that it can be used in the template. + +Next, open `src/app/profile/profile.component.html` and replace its contents with the following: + +```html +
+{{ profile | json }}
+
+``` + +The `AuthService`'s `userProfile$` observable emits the user profile object when it becomes available in the application. By using the [`async` pipe](https://angular.io/api/common/AsyncPipe), we can display the profile object as JSON once it's been emitted. + +Next we need a route for the profile page. Open `src/app/app-routing.module.ts`, import the `ProfileComponent`, and add it to the `routes` array to create a `/profile` route: + +```js +... +import { ProfileComponent } from './profile/profile.component'; + +const routes: Routes = [ + ..., + { + path: 'profile', + component: ProfileComponent + } +]; +... +``` + +Finally, the navigation bar should be updated to include navigation links. Open `src/app/nav-bar/nav-bar.component.html` and add route links below the login/logout buttons: + +```html +
+ ... + + Home  + Profile +
+``` + +> **Checkpoint**: If you log in now, you should be able to click on the **Profile** link to access the `/profile` page component and view your user data. Log out of the application to make sure that the profile link is no longer displayed when unauthenticated. + +## Add an Authentication Guard + +Right now, users could enter the `/profile` path in the browser URL to view profile page, even if they're unauthenticated. They won't see any user data (since they're not logged in), but they can still access the page component itself. Let's fix this using a [route guard](https://angular.io/guide/router#milestone-5-route-guards). + +The Angular CLI can be used to generate a guard: + +```bash +ng generate guard auth +``` + +You will receive this prompt: "Which interfaces would you like to implement?" Select **CanActivate**. + +Open the `src/app/auth.guard.ts` file and replace its contents with the following: + +```ts +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + RouterStateSnapshot, + UrlTree, + CanActivate, +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from './auth.service'; +import { tap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthGuard implements CanActivate { + constructor(private auth: AuthService) {} + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean { + return this.auth.isAuthenticated$.pipe( + tap((loggedIn) => { + if (!loggedIn) { + this.auth.login(state.url); + } + }) + ); + } +} +``` + +The `canActivate()` method can return an `observable`, `promise`, or `boolean`. This tells the application whether or not navigation to the guarded route should be allowed to proceed depending on whether the returned value is `true` or `false`. + +The `AuthService` provides an observable that does exactly this, returning a boolean indicating if the user is authenticated: `isAuthenticated$`. + +We can return `isAuthenticated$` and implement a side effect with the `tap` operator to check if the value is `false`. If so, the user is not logged in, so we can call the `login()` method to prompt the user to authenticate. Passing the `state.url` as a parameter tells our authentication service's `login()` function that we want the application to redirect back to this guarded URL after the user is logged in. + +Now we need to apply the `AuthGuard` to the `/profile` route. Open the `src/app/app-routing.module.ts` file and import the guard, then add it to the profile: + +```js +... +import { AuthGuard } from './auth.guard'; + +const routes: Routes = [ + ... + { + path: 'profile', + component: ProfileComponent, + canActivate: [AuthGuard] + } +]; +... +``` + +> Guards are arrays because multiple guards can be added to the same route. They will run in the order declared in the array. + +> **Checkpoint:** Log out of your app and then navigate to `http://localhost:3000/profile`. You should be automatically redirected to the Auth0 login page. Once you've authenticated successfully, you should be redirected to the `/profile` page. diff --git a/02-Calling-an-API/TUTORIAL.md b/02-Calling-an-API/TUTORIAL.md new file mode 100644 index 00000000..cd8de29f --- /dev/null +++ b/02-Calling-an-API/TUTORIAL.md @@ -0,0 +1,371 @@ +# Tutorial + +_This tutorial demonstrates how to make API calls for protected resources on your server._ +Most single-page apps use resources from data APIs. You may want to restrict access to those resources, so that only authenticated users with sufficient privileges can access them. Auth0 lets you manage access to these resources using [API Authorization](/api-auth). + +This tutorial shows you how to create a simple API using [Express](https://expressjs.com) that serves resources protected by a middleware that looks for and validates access tokens. You will then see how to call this API using an access token granted by the Auth0 authorization server. + +Most single page applications use resources from data APIs. You may want to restrict access to those resources, so that only authenticated users with sufficient privileges can access them. Auth0 lets you manage access to these resources using [API Authorization](/api-auth). + +This tutorial shows you how to create a simple API using [Express](https://expressjs.com) that validates incoming JSON Web Tokens. You will then see how to call this API using an Access Token granted by the Auth0 authorization server. + +## Create an API + +In the [APIs section](https://manage.auth0.com/#/apis) of the Auth0 dashboard, click **Create API**. Provide a name and an identifier for your API. +You will use the identifier later when you're configuring your Javascript Auth0 application instance. +For **Signing Algorithm**, select **RS256**. + +![Create API](media/create-api.png) + +## Create the Backend API + +For this example, you'll create an [Express](https://expressjs.com/) server that acts as the backend API. This API will expose an endpoint to validate incoming [JWT-formatted access tokens](https://auth0.com/docs/tokens/concepts/jwts) before returning a response. + +Start by installing the following packages: + +```bash +npm install cors express express-jwt jwks-rsa npm-run-all +``` + +- [`express`](https://github.com/expressjs/express) - a lightweight web server for Node +- [`express-jwt`](https://www.npmjs.com/package/express-jwt) - middleware to validate JsonWebTokens +- [`cors`](https://github.com/expressjs/cors) - middleware to enable CORS +- [`jwks-rsa`](https://www.npmjs.com/package/jwks-rsa) - retrieves RSA signing keys from a JWKS endpoint +- [`npm-run-all`](https://www.npmjs.com/package/npm-run-all) - a helper to run the SPA and backend API concurrently + +Next, create a new file `server.js` with the following code: + +```js +const express = require("express"); +const cors = require("cors"); +const jwt = require("express-jwt"); +const jwksRsa = require("jwks-rsa"); + +// Create a new Express app +const app = express(); + +// Accept cross-origin requests from the frontend app +app.use(cors({ origin: "http://localhost:3000" })); + +// Set up Auth0 configuration +const authConfig = { + domain: "YOUR_AUTH0_DOMAIN", + audience: "YOUR_API_IDENTIFIER", +}; + +// Define middleware that validates incoming bearer tokens +// using JWKS from ${account.namespace} +const checkJwt = jwt({ + secret: jwksRsa.expressJwtSecret({ + cache: true, + rateLimit: true, + jwksRequestsPerMinute: 5, + jwksUri: `https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json`, + }), + + audience: authConfig.audience, + issuer: `https://YOUR_AUTH0_DOMAIN/`, + algorithm: ["RS256"], +}); + +// Define an endpoint that must be called with an access token +app.get("/api/external", checkJwt, (req, res) => { + res.send({ + msg: "Your Access Token was successfully validated!", + }); +}); + +// Start the app +app.listen(3001, () => console.log("API listening on 3001")); +``` + +The above API has one available endpoint, `/api/external`, that returns a JSON response to the caller. This endpoint uses the `checkJwt` middleware to validate the supplied bearer token using your tenant's [JSON Web Key Set](https://auth0.com/docs/jwks). If the token is valid, the request is allowed to continue. Otherwise, the server returns a 401 Unauthorized response. + +Finally, modify `package.json` to add two new scripts: `dev` and `server`. Running the `dev` script will now start both the backend server and the serve the Angular application at the same time: + +```json +"scripts": { + ..., + "server": "node server.js", + "dev": "npm-run-all --parallel start server" +}, +``` + +To start the project for this sample, run the `dev` script from the terminal: + +```bash +npm run dev +``` + +This will start both the backend API and the frontend application together. + +### Proxy to Backend API + +In this example, the Node backend and Angular app run on two different ports. In order to call the API from the Angular application, the development server must be configured to proxy requests through to the backend API. This is so that the Angular app can make a request to `/api/external` and it will be correctly proxied through to the backend API at `http://localhost:3001/api/external`. + +To do this, create a `proxy.conf.json` file in the root of the project and add the following code: + +```json +{ + "/api": { + "target": "http://localhost:3001", + "secure": false + } +} +``` + +Now open `angular.json` and add a reference to the proxy config. In the `serve` node, include the following reference to the proxy config file: + +```json + ... + "serve": { + ..., + "options": { + ..., + "proxyConfig": "proxy.conf.json" + }, + ... +``` + +> In order for these changes to take effect, you will need to stop and restart the run script. + +## Update the Authentication Service + +We'll now make several updates in the `src/app/auth.service.ts` file. + +### Add Audience + +First, add the `audience` value to the creation of the Auth0 client instance: + +```js +// Create an observable of Auth0 instance of client +auth0Client$ = (from( + createAuth0Client({ + ..., + audience: "YOUR_AUTH0_API_IDENTIFIER" +}) +``` + +This setting tells the authorization server that your application would like to call the API with the identifier \${apiIdentifier} on the user's behalf. This will cause the authorization server to prompt the user for consent the next time they log in. It will also return an access token that can be used to call the API. + +### Manage Access Token + +We'll define an observable method to retrieve the access token and make it available for use in our application. Add the following to the `AuthService` class: + +```ts +getTokenSilently$(options?): Observable { + return this.auth0Client$.pipe( + concatMap((client: Auth0Client) => from(client.getTokenSilently(options))) + ); +} +``` + +If you'd like to [pass options to `getTokenSilently`](https://auth0.github.io/auth0-spa-js/classes/auth0client.html#gettokensilently) when calling the method, you can do so. + +> **Why isn't the token stored in browser storage?** Historically, it was common to store tokens in local or session storage. However, browser storage is [not a secure place to store sensitive data](https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html#local-storage). The [`auth0-spa-js` SDK](https://auth0.com/docs/libraries/auth0-spa-js) manages session retrieval for you so that you no longer need to store sensitive data in browser storage in order to restore sessions after refreshing a Single Page Application. + +With `auth0-spa-js`, we can simply request the token from the SDK when we need it (e.g., in an HTTP interceptor) rather than storing it locally in the Angular app or browser. The SDK manages token freshness as well, so we don't need to worry about renewing tokens when they expire. + +## Create an HTTP Interceptor + +In order to add the access token to the header of secured API requests, we'll create an [HTTP interceptor](https://angular.io/api/common/http/HttpInterceptor). Interceptors intercept and handle or modify HTTP requests or responses. Interceptors in Angular are services. + +Create an interceptor service with the following CLI command: + +```bash +ng generate service interceptor +``` + +Open the generated `src/app/interceptor.service.ts` file and add the following code: + +```ts +import { Injectable } from "@angular/core"; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor, +} from "@angular/common/http"; +import { AuthService } from "./auth.service"; +import { Observable, throwError } from "rxjs"; +import { mergeMap, catchError } from "rxjs/operators"; + +@Injectable({ + providedIn: "root", +}) +export class InterceptorService implements HttpInterceptor { + constructor(private auth: AuthService) {} + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + return this.auth.getTokenSilently$().pipe( + mergeMap((token) => { + const tokenReq = req.clone({ + setHeaders: { Authorization: `Bearer <%= "${token}" %>` }, + }); + return next.handle(tokenReq); + }), + catchError((err) => throwError(err)) + ); + } +} +``` + +The `AuthService` is provided so that we can access the `getTokenSilently$()` stream. Using this stream in the interceptor ensures that requests wait for the access token to be available before firing. + +The `intercept()` method returns an observable of an HTTP event. In it, we do the following: + +- `mergeMap` ensures that any new requests coming through don't cancel previous requests that may not have completed yet; this is useful if you have multiple HTTP requests on the same page +- `clone` the outgoing request and attach the [`Authorization` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) with access token, then send the cloned, authorized request on its way + +In order to put the interceptor to work in the application, open the `src/app/app-routing.module.ts` routing module and add: + +```js +... +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { InterceptorService } from './interceptor.service'; +... +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: InterceptorService, + multi: true + } + ] +}) +``` + +In the `providers` array, we're providing the `HTTP_INTERCEPTORS` injection token and our `InterceptorService` class. We then set `multi: true` because we could potentially use multiple interceptors (which would be called in the order provided). + +> The interceptor will now run on every outgoing HTTP request. If you have public endpoints as well as protected endpoints, you could use conditional logic in the interceptor to only add the token to requests that require it. + +## Call the API + +Now we're ready to call the secure API endpoint and display its results in the application. To make HTTP requests in our application, we'll first add the `HttpClientModule` to the `src/app/app.module.ts`: + +```js +... +import { HttpClientModule } from '@angular/common/http'; +... +@NgModule({ + ..., + imports: [ + BrowserModule, + AppRoutingModule, + HttpClientModule + ], + ... +}) +... +``` + +The `HttpClientModule` must be imported and then added to the NgModule's `imports` array. + +Next, create an API service using the Angular CLI: + +```bash +ng generate service api +``` + +Open the generated `src/app/api.service.ts` file and add: + +```ts +import { Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { Observable } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class ApiService { + constructor(private http: HttpClient) {} + + ping$(): Observable { + return this.http.get("/api/external"); + } +} +``` + +This creates a `ping$()` method that returns an observable of the HTTP GET request to the API. This can now be used in the application to call the `api/external` endpoint. + +Now we need somewhere to call the API and display the results. Create a new page component: + +```bash +ng generate component external-api +``` + +Open `src/app/external-api/external-api.component.ts` and add this code: + +```ts +import { Component, OnInit } from "@angular/core"; +import { ApiService } from "src/app/api.service"; + +@Component({ + selector: "app-external-api", + templateUrl: "./external-api.component.html", + styleUrls: ["./external-api.component.css"], +}) +export class ExternalApiComponent implements OnInit { + responseJson: string; + + constructor(private api: ApiService) {} + + ngOnInit() {} + + pingApi() { + this.api.ping$().subscribe((res) => (this.responseJson = res)); + } +} +``` + +The API service is provided and a named subscription to `api.ping$()` is created. The act of subscribing fires off the HTTP call, which we'll do on a button click in the UI. When data comes back from the API, the results are set in a local property (`responseJson`). + +> We do _not_ need to unsubscribe from the `api.ping$()` observable because it completes once the HTTP request is finished. + +Open `src/app/external-api/external-api.component.html` and replace its contents with the following: + +```html + + +
+{{ responseJson | json }}
+
+``` + +Clicking the button calls the API. The response is displayed in the `pre` element, which only renders if `responseJson` is present. + +This route now needs to be added to our application. Open `src/app/app-routing.module.ts` and add the `/external-api` route like so: + +```js +... +import { ExternalApiComponent } from './external-api/external-api.component'; +... +const routes: Routes = [ + ..., + { + path: 'external-api', + component: ExternalApiComponent, + canActivate: [AuthGuard] + } +]; +... +``` + +The `/external-api` route is also guarded with `AuthGuard` since it requires an authenticated user with an access token. + +Finally, add a link to the navigation bar. Open `src/app/nav-bar/nav-bar.component.html` and add: + +```html +
+ ... External API +
+``` + +> If, at any time, the application isn't working as expected, the Angular CLI server may need to be restarted. Enter `Ctrl+C` in the terminal to stop the application, then run it again using `npm run dev`. + +> **Checkpoint:** You should now be able to log in, browse to the External API page, and click the **Ping API** button to make an API request. The response should then display in the browser. diff --git a/02-Calling-an-API/media/create-api.png b/02-Calling-an-API/media/create-api.png new file mode 100644 index 00000000..26c53e74 Binary files /dev/null and b/02-Calling-an-API/media/create-api.png differ