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;
}
}
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();
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:
- You use the identifier from the Keycloak server
- 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();
}
}
}
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>
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);
}
}
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:
So that wraps up our journey using Keycloak, Quarkus and Angular.
Top comments (0)