Keycloak with Angular and Spring Security

Keycloak is one of the widely used Open Source Identity and Access Management application. This article demonstrates usage of Keycloak as authentication server with Angular, Spring Boot and Spring Security. As usual, code uploaded to Github repository

Technologies:

  1. Spring Boot 2.x
  2. Spring Security
  3. Angular 15.x
  4. Keycloak 20.x.x
  5. Spring Data JPA
  6. Mysql 5+
  7. Docker

Prerequisites:

  1. Follow instructions from official docker documentation and install docker on your local machine
  2. This article uses Keycloak and Mysql docker containers for the setup. But if you prefer standalone versions, follow instructions from official Keycloak docs to install Keycloak and create a database in your local Mysql server

Setup:

  1. Clone the Github repository and navigate to /src/main/resources/docker directory.
  2. Execute the following command to bring up Keycloak and MySql containers
$ docker-compose -f keycloak-mysql.yml up

3. Go to http://localhost:8080 to login into Keycloak admin console and use the following credentials. Same can be configured in keycloak-mysql.yml file before starting containers

username: admin
password: Test@2020

and home page should look like this

4. In Keycloak realms are similar to domains in Active directory. To add a new group or domain, point your mouse on Master and click on Add Realm and name it as keycloakdemo

5. In Login tab enable everything and select Require SSL to external requests

6. Click on clients to see a list of existing clients and click create button to create a new client named angular-app

7. Select angular-app from clients list and modify the following values in the Settings tab then save the page

Valid Redirect URIs: http://localhost:4200/*
BaseU URL: http://localhost:4200
Web Origins: *

8. Go to configure → Roles → Add Role and give RoleName as Admin and if you want to use a different name, make sure to change the value in src/main/java/com/pj/keycloak/security/Roles.java class

Roles.java

class Roles{
    static final String ADMIN="Admin";
    private Roles(){}
}

9. Go to Manage → Users → Add User and enter the values as shown below

10. Select the created user from the Users list and navigate to the Credentials tab. Set the password to Test@2020

11. Click on Role Mappings and bring Admin role from Available Roles to Assigned Roles

12. Go to local Mysql server and create a database named keycloak-springsecurity. This database holds our application data. Note that this different from Keycloak database.

How to Run?

  1. Go to KeyCloakSpringSecurityApplication class and run it. If everything works as expected, you should be able to browse the REST API through http://localhost:8081
  2. Go to the database server and execute the following command to insert sample employee data
INSERT INTO `keycloak-springsecurity`.`employee`
(`id`,`email`,`first_name`,`last_name`,`phone`) VALUES (
1001,'jdoe@example.com','John','Doe','389-399-3893');

3. Go to src/main/webapp/spring-data-ui directory and execute the following command to install dependencies

$ npm install

4. Execute the following command to start the Angular application and it will be available on http://localhost:4200

$  ng serve --watch

5. Go to http://localhost:4200 to see Angular home page and it should automatically redirect you to KeyCloak login page. Enter credentials for John Doe you created earlier. And you should see the following screen

Angular home page

Click on Employees to see list of employees. That’s it, Spring Boot REST API and Angular app protected through Keycloak Server

Deep dive into Code

  1. Let’s look at the Spring code first. We have an Employee class with some basic fields and controller, service and repository classes defined as below

Employee.java

@Entity
@Table(name = "employee")
@Data
public class Employee implements Serializable
{
private static final long serialVersionUID = -2482579485413606056L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

@Column(name = "email")
private String email;

@Column(name = "phone")
private String phone;


@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Employee employee = (Employee) o;
return getId().equals(employee.getId());
}

@Override
public int hashCode()
{
return Objects.hash(getId());
}
}

EmployeeController.java

@RestController
@RequestMapping("/api/v1/employee")
public class EmployeeController
{
private final EmployeeService employeeService;

public EmployeeController(EmployeeService employeeService)
{
this.employeeService = employeeService;
}

@GetMapping(path = "/list")
public List<Employee> findAll()
{
return employeeService.findAll();
}

@GetMapping(path = "/find/{id}")
public Optional<Employee> findById(@PathVariable Long id)
{
return employeeService.findById(id);
}
}

EmployeeServiceImpl.java

@Service
public class EmployeeServiceImpl implements EmployeeService
{
private final EmployeeRepository employeeRepository;

public EmployeeServiceImpl(EmployeeRepository employeeRepository)
{
this.employeeRepository = employeeRepository;
}

@Override
public List<Employee> findAll()
{
return employeeRepository.findAll();
}

@Override
public Optional<Employee> findById(Long id)
{
return employeeRepository.findById(id);
}

}

EmployeeRepository.java

public interface EmployeeRepository extends JpaRepository<Employee,Long>{
}

2. SecurityConfig class takes care of securing the application with KeycloakWebSecurityConfigurerAdapter class, which provides a convenient base class for creating a WebSecurityConfigurer instance secured by Keycloak. This implementation allows customization by overriding methods.

3. Method configureGlobal() gets KeyCloakAuthenticationAdapter instance and sets the authentication provider to keycloakAuthenticationProvider. Spring Security uses keycloakAuthenticationProvider to perform authentication

@Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder){
        KeycloakAuthenticationProvider keycloakAuthenticationProvider=keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        authenticationManagerBuilder.authenticationProvider(keycloakAuthenticationProvider);
    }

4. Bean KeycloakSpringBootConfigResolver helps to connect to Keycloak based on config properties from application.yml file

5. configure() method is key to the whole application, which secures the Endpoints with required roles

@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);

http.authorizeRequests()
.antMatchers("/api/**")
.hasAnyRole(Roles.ADMIN)
.anyRequest()
.permitAll();

http.cors();
}

6. application.yml file contains Keycloak server properties, realm and client details.

# Server properties
server:
port: 8081

#Keycloak Properties
keycloak:
auth-server-url: http://localhost:8080/auth
realm: keycloakdemo
resource: angular-app
public-client: true
principal-attribute
: preferred_username
.........
......

7. Now, in Angular application, keycloak-js library helps to config and connect to Keycloak server and perform authentication. The service KeycloakService class uses the keycloak-js library classes to achieve this and loads it when initializing the application

keycloak.service.ts

import {Injectable} from "@angular/core";
import * as Keycloak from "keycloak-js";
import {KeycloakInstance} from "keycloak-js";

@Injectable({
providedIn: 'root'
})
export class KeycloakService
{
private keycloakAuth: KeycloakInstance;

constructor()
{
}

init(): Promise<any>
{
return new Promise((resolve, reject) =>
{
const config = {
'url': 'http://localhost:8080/auth',
'realm': 'keycloakdemo',
'clientId': 'angular-app'
};
// @ts-ignore
this.keycloakAuth = new Keycloak(config);
this.keycloakAuth.init({onLoad: 'login-required'})
.success(() =>
{
resolve();
})
.error(() =>
{
reject();
});
});
}

getToken(): string
{
return this.keycloakAuth.token;
}

logout()
{
const options = {
'redirectUri': 'http://localhost:4200',
'realm': 'keycloakdemo',
'clientId': 'angular-app'
};
this.keycloakAuth.logout(options);
}
}

8. In app.module.ts file we inject this class and call init() method when the application initializes

export function kcFactory(keycloakService: KeycloakService) {
return () => keycloakService.init();
}
@NgModule({
declarations: [
AppComponent,
EmployeeListComponent,
LogoutComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [KeycloakService,
{
provide: APP_INITIALIZER,
useFactory: kcFactory,
deps: [KeycloakService],
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}],
bootstrap: [AppComponent]
})
export class AppModule { }

9. Class TokenInterceptor implements HttpInterceptor interface, which helps to intercept every HTTP request and set the Bearer authentication token.

@Injectable()
export class TokenInterceptor implements HttpInterceptor
{
constructor(private kcService: KeycloakService)
{
}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
const authToken = this.kcService.getToken() || "";
request = request.clone({
setHeaders: {
"Authorization": "Bearer " + authToken
}
});
return next.handle(request);
}
}

All the code combined into one repository and uploaded to Github. Please let me know if you have any questions. Happy coding 🙂

Pavan Kumar Jadda
Pavan Kumar Jadda
Articles: 36

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.