Securing Angular and Quarkus with Keycloak Pt 3
Published: 2020-10-08 18:01:42

Coming soon...# Securing Angular and Quarkus with Keycloak Pt 3

In our last article we left with some secure endpoints that were serving up content to the Angular application we built.

In this installment we will wrap up with using the roles and user profile to load user specfic data with our RewardsComponent.

Code for this article is [here](https://github.com/cloudy-engineering/pet-store-api/tree/user-profiles) (API) and the UI code [here](https://github.com/cloudy-engineering/pet-store-ui/tree/rewards).

## Rewards Component

If you refer back to the previous article you'll see we created the RewardsComponent and endpoint but didn't do much with it. The goal of this article is to determine who is calling the endpoint through their user profile and load data using their User Id.

We will start with the API.

## User Profile in the API

Quarkus makes idenitifying the user rather easy using the `io.quarkus.security.identity.SecurityIdentity` class.

We just need to inject it into our RewardsResource:

```java
@Path("/v1/rewards")
public class RewardsResource {

    @Inject
    SecurityIdentity identity;

    @GET
    public Response getRewards() {
        return null;
    }
}
```

The SecurityIdentity gives you access to:

* Permissions
* Roles
* Principal
* Credentials

In order to determine who is making the secured request we can use:

```java
String username = this.identity.getPrincipal().getName();
```

From an architecture standpoint the user id that Keycloak gives you is going to be constant. When it comes to your resource services will probably want to make sure either:

1. You use the identifier from the Keycloak server
1. You map the id from Keycloak to an internal identifier.

For the sake of this article we will just use the Keycloak identifier instead of creating a mapped id.

I've added in a database migration to our service to create accounts and store them in a table and added code to to retrieve them.

The data we are storing includes:

|  Reward Id | Owner | Reward Type |
| ---------- | ----- | ----------- |
| 1          | charlene.masters | '20% Discount' |
| 2          | charlene.masters | 'BOGO Free' |
| 3          | mary.contrary    | '10% Discount' |
| 4          | mary.contrary    | '25% Discount' |
| 5          | mary.contrary    | 'invite a friend' |

And the code that we will use server side includes:
 
**RewardsResource.java**
```java
package com.cloudyengineering.pets;

import io.agroal.api.AgroalDataSource;
import io.quarkus.security.identity.SecurityIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

@Path("/v1/rewards")
@Produces("application/json")
public class RewardsResource {

    private final Logger log = LoggerFactory.getLogger(RewardsResource.class);

    @Inject
    SecurityIdentity identity;

    @Inject
    AgroalDataSource defaultDataSource;

    public RewardsResource() {
    }

    @GET
    @RolesAllowed({"api-customer"})
    public Response getRewards() {
        String username = this.identity.getPrincipal().getName();
        List<Reward> rewards = new ArrayList<>();
        try {
            String sql = "select * from rewards where owner = ?";
            PreparedStatement stmt = defaultDataSource.getConnection().prepareStatement(sql);
            stmt.setString(1, username);
            ResultSet rs = stmt.executeQuery();
            while(rs.next()) {
                Reward reward = new Reward();
                reward.setRewardId(rs.getInt("reward_id"));
                reward.setOwner(rs.getString("owner"));
                reward.setRewardType(rs.getString("reward_type"));
                rewards.add(reward);
            }
            return Response.ok(rewards).build();
        } catch(Exception e) {
            e.printStackTrace();
            return Response.serverError().build();
        }
    }

}
```

## What have we updated?

As you can see, aside from implementing the logic to load the data from a database, we've annotated our endpoint with the `@RolesAllowed()` annotation limiting access to users with the `api-customer` role. We've also used the `SecurityIdentity` to get the name of the user that is requesting the data allowing us to filter the query to that users' data only.


# Let's update the User Interface!

**rewards.component.html**
```html
<p>rewards works!</p>
<table>
    <tr>
        <th>Reward Id</th>
        <th>Reward Type</th>
    </tr>
    <tr *ngFor="let reward of rewards | async">
        <td>{{reward.reward_id}}</td>
        <td>{{reward.reward_type}}</td>
    </tr>
</table>
```

**rewards.component.ts**
```typescript
import { Component, OnInit } from '@angular/core';
import { StoreService } from '../store.service';
import { map } from 'rxjs/operators';
import { Reward } from '../_model';
import { BehaviorSubject, Observable } from 'rxjs';

@Component({
  selector: 'app-rewards',
  templateUrl: './rewards.component.html',
  styleUrls: ['./rewards.component.css']
})
export class RewardsComponent implements OnInit {

  rewards = new BehaviorSubject<Reward[]>([]);

  constructor(private store: StoreService) { }

  ngOnInit(): void {
    const reward$ = this.store.getRewards().pipe( 
      map(results => {
        this.rewards.next(results);
      })
    );

    reward$.subscribe(data => data);
  }

}
```

Not much to report here except that we are using a `BehaviourSubject` to store the data from the `StoreService` to ensure async access to the data. This is why we are using the `async` pipe in the `rewards.component.html`.

# Running our Code

If you run our service and UI you should now be able to log in as either Mary or Charlene and see only the rewards that belong to the user:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fyhbemzvjowtkl8je0kb.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tix85wzd0e1n5muol9hl.png)

So that wraps up our journey using Keycloak, Quarkus and Angular.