d

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.

15 St Margarets, NY 10033
(+381) 11 123 4567
ouroffice@aware.com

 

KMF

Token-based Security: Angular Applications – Part 1

Introduction

I have written few posts on token-based security, its importance, OAuth, OIDC, and Identity-Server. You can check the previous posts if you are new to these topics.

Today, I will write about how to secure angular applications with these technologies. We will see how to wire the Angular applications with Identity-Server.

Angular application is a public client type of application, these applications are not capable of keeping a secret like server-side client applications e.g. ASP .NET MVC, Nodejs, etc.

In the past, Implicit flow was used for angular but it’s no longer recommended. As we learned in previous posts that AuthorizationCode + PKCE is the norm today.

This flow involves redirection to Identity-Server from an angular app and their user enters Login/Password on IDP, validated there, and then redirected back to the angular application.

I have seen many applications with in-app login screens (e.g. inside an Angular application), though this can be done (usually using Resource-Owner Password Flow), but if possible, avoid this practice. It creates more issues than it solves. If you are not happy with IDP default views for login/logout, you can rather style/change IDP screens instead of building login/logout etc. screens inside your angular application.

In this post, we will cover the recommended flow, but, first, we need some API endpoint (on resource server) that will return some data. Let’s create a simple endpoint next:

API Endpoint

I have an API, which is just a .NET Core web application. To start with, I created an API for Products as shown below, which uses Dapper and PostgreSQL to read data from the database and return it to the caller in JSON format:

(I am using Postgres database with dapper ORM, you can actually just return in-memory data and skip the DB part totally if you like).

Next, we will create an angular application and a Products component. Angular application, will make an HTTP call to the Get endpoint on ProductsController and receive the JSON data which it will then display using HTML.

Angular Application

I have created a very basic angular application using angular-CLI. The application contains a product component that displays products data in a table. You can use your own existing or new angular application and the mechanism will be the same.

This is also a service api.service.ts which makes HTTP calls to backend API endpoint (ProductsController):

Client App Angular

Now, if we run the angular app and visit the producProduct list angularts page, the product is displayed on the page:

So, our angular application is able to call the API endpoint and get the data. Next, let’s protect this API endpoint:

Protecting the API

Let’s protect the API by applying Authorize attribute:

Protecting the API

So, the API is protected and only authorized clients can access it.

Now, at this point, we have a Products API endpoint in resource-server which is protected using Authorize attribute. Our IdentityServer is protecting the resource server. We also have an Angular application, which makes an HTTP call to the Products API endpoint. Next, let’s make the necessary steps to make this HTTP call authorize to get data.

OIDC Client

To wire up our angular application to the authorization server and to deal with various flows, we can use oidc client library to manage this redirection and protocol level details for us.

You can install the package using the npm command: npm install oidc-client

Next, we will create a service (AuthService) that will act as a security context for our angular application. This service will internally use oidc library as needed.

Auth Service

We will start by creating this service. Auth service will use oidc library and will do the hard work of authenticating with oidc for us.

Auth service

first, we will import UserManager and User type from oidc library. These are the primary types you’ll deal with from oidc client.

Next, we included private fields for UserManger and User type. UserManager will produce a User at the completion of the login process. Next, we initialized UserManager in the constructor of AuthService.

We will see the user variable shortly.

  • UserManager object manages all the low-level protocol details of OIDC flows for us. So, we don’t have to worry about what’s going on at a wire-level between our app and identity provider.
  • User type encapsulates the client-side info about a signed-in user, such as
    • ID and Access Tokens returned from IDP
    • Any user Profile information returned from IDP as Claims.
    • and being able to when the tokens for the user are expired.

In the constructor of service, we are initializing UserManager with stsSettings. If you remember from our Postman demos, you may recognize a few of those already.

  • redirect_uri: This is the URI when IdentityServer redirects back after the login process. We will create an angular component for this.
  • post_logout_redirect_uri: Similar to above, we will create an angular component to deal with when users log out on STS and are redirected back to the angular application.

These two components will be very simple, mostly with only one empty div and some JavaScript. We will see these two components later in the post.

Login Process

Next, I added a login method in AuthService, which call singinRedirect on userManager object:

AuthService login method

After successful login, UserManager stores in session storage the resulting user object that it creates so that it can be retrieved anytime it is needed, such as to get the AccessToken to send it to the API calls.

User object contains the Access and ID Tokens, as well as an expired flag which we can check to make sure that the access token is not expired.

Access token not expired

isLoggedIn Helper Method

In the code picture above, I created isLoggedIn a method. This method will help us find out from any component that “check if user is already logged in“.

Raising Event When User Is Loaded

When the redirect comeback into the angular app from the STS, the process that obtains the ID and ACCESS TOKENS happens asynchronously from the loading of the root view. So, we’ll also need to raise an event when the user is loaded (see image below), so UI that depends on that can be updated. We’ll use an RxJs observable to do that:

Angular RxJs observable

So, to achieve this, we will declare a private property _loginChangedSubject on AuthService that is an RxJs subject and then a public observable _loginChanged that is produced by the subject. We then add code to fire that is observable in the isLoggedIn method.

Check if user is logged in

We’ll also fire this observable when the login process completes, but we will come back to that part a little bit later.

Loading User From Session Storage

Next, update root AppComponent with IsLoggedIn check so the user gets loaded on launch if there’s already one in the session storage from a previous login.

Loading User From Session Storage

I’ve also added an event handler to update the isLoggedIn property when the logged-in status changes.

So, the app component will be now notified whenever user status changes and the application will be able to react to that change.

Login/Logout Buttons and Method

Next, I added Login/Logout Buttons (HTML) and method in AppComponent:

Login/Logout Buttons

and here is the login/logout methods code:

Login/Logout Methods

We still have to set up a login and sign-out callback components and some more angular code. We also need to configure STS with angular client settings. However, to keep post length in control, we will pause now and will continue in the next post for the remaining setup. You can download the source code from this git repository. We will continue this setup in the next post, if you have some comments or questions, let me know. Till next time, Happy Coding.

Credit: Source link

Previous Next
Close
Test Caption
Test Description goes like this