Token-based Security: Angular Applications – Part 1
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:
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.
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):
Now, if we run the angular app and visit the products 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:
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.
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.
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.
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.
Next, I added a login method in AuthService, which call
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.
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:
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.
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.
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:
and here is the login/logout methods code:
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