DEV Community

Anthony Ikeda
Anthony Ikeda

Posted on • Updated on

Securing Angular and Quarkus with Keycloak Pt 3

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 (API) and the UI code here.

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:

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

    @Inject
    SecurityIdentity identity;

    @GET
    public Response getRewards() {
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

The SecurityIdentity gives you access to:

  • Permissions
  • Roles
  • Principal
  • Credentials

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

String username = this.identity.getPrincipal().getName();
Enter fullscreen mode Exit fullscreen mode

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
  2. 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

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();
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

rewards.component.ts

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);
  }

}
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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

Discussion (0)