Spring Security Oauth2 with Keycloak – News Couple
ANALYTICS

Spring Security Oauth2 with Keycloak


This article was published as a part of the Data Science Blogathon.

Introduction

What is PKCE?

PKCE stands for Proof key code enhanced authorization code flow. This grant type was created to be used for public-facing clients, like web applications developed
using angular, react, vue and etc, and also mobile applications. PKCE authorization code flow is mainly considered a best practice to follow when we are using public clients.

Why PKCE?

code_challenge — which is a base 64 encoded random string which is generated by hashing and encoding in another value generated by the client called a code verifier.

code_challenge_method— it should be configured inside our authorization server when we are first configuring the client. The recommended value for this is S256, which is a cryptographic hashing function.

Flow

1. Once the client makes this request to the authorization server the server responds with a login page asking the user to authenticate.

2. Once the user logged in the authorization server returns the authorization code similar to the authorization code flow but does not request the access token the client should send the code verifier value along with the authorization code as part of the post request to the token endpoint .

3. The authorization server receives the post request, validates the authorization code and the code verifier values, and then responds with the access token and idToken pair.

Practical

Let’s see our flow with the practical example.

Keycloak Dashboard Configurations

For this keycloak dashboard configuration, you need to install and run the keycloak server.

At the start login to the key cloak administration console.

Keycloak dashboard
Keycloak
Keycloak dashboard set up

Create Client on Keycloak

create client
Keycloak

Configure Client on Keycloak

Here we have to make some changes, enable access type as public, standard flow enabled, provide value for valid redirect URI (http://localhost:4200 — angular app address), provide web origin (allow CORS which can access the authorization server , as for now I am going to provide *here, for permit all origins).

configure client
Keycloak configure client

Note: When we are using a production application, please don’t provide the * value here, only provide the valid origin of the redirect URI, that means if your front-end application is running on a server provide the host details of the server instead of allowing all origins.

Keycloak

The next value that needs to configure is the PKCE enhanced code_challenge_method, you can find this value under the advanced settings.

That’s it for the client configuration, let’s dive into the code.

Angular Configuration

Open our angular project & first need to install the npm package called angular-oauth2-oidc.

Angular confirmation

After adding this package, run the ‘npm install’ command to ad this package.

After the package is installed, I am creating a new file called “auth.config.ts”. Inside the file, I am providing six fields.

1. issuer: Issuer URI contains all the list of configuration endpoint which is exposed by the authorization server. ‘http://localhost:8180/realms/oauth2-demo-realm’

Keycloak
Keycloak

2. redirectUri: Same value when configuring the client in the keycloak section, instead of hard coding this value I am providing ‘window.location.origin

3. clientId: It is from our keycloak, value is ‘oauth2-demo-pkce-client

Keycloak

4. ResponseType: This is going to be a ‘code ‘ as we are following the authorization code flow mechanism.

5. strictDiscoveryDocumentValidation — This is something that is relevant to the angular oauth2 oidc library, this is used because the list of endpoints exposed by the issuer URI endpoint does not contain the same base URI as the issuer URI. In our case the issuer URI uses the same base URI (http://localhost:8180), so we can use it as true, if not provide false.

Keycloak document

6. scope: Here I am providing some default scope.

import AuthConfig from 'angular-oauth2-oidc';
export const authConfig: AuthConfig = 
  issuer: 'http://localhost:8180/realms/oauth2-demo-realm',
  redirectUri: window.location.origin,
  clientId: 'oauth2-demo-pkce-client',
  responseType: 'code',
  strictDiscoveryDocumentValidation: true,
  scope: 'openid profile email offline_access',

Then inject the oath2 service class, and provide the configs

import Component from '@angular/core';
import OAuthService from "angular-oauth2-oidc";
import authConfig from "./auth.config";
@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
)
export class AppComponent 
  title="frontend";
  text="";
  constructor(private oauthService: OAuthService) 
    this.configure();
  
login() 
    this.oauthService.initCodeFlow();
  
private configure() 
    this.oauthService.configure(authConfig);
    this.oauthService.loadDiscoveryDocumentAndTryLogin(); // This method is trigger issuer uri
  
logout() 
    this.oauthService.logOut();
  

Till now we configured the files, but not calling this from our Html file. Let’s do it.

Then configure app.component.html with the login and logout button

Login

Logout

Finally, we have to define our OAuth module to our app.module.ts

Note: The upcoming configuration should be done after the spring boot application configurations are finished.

Create the component app.service.ts

Here make an HTTP get request throughout our resource server (Spring-boot application)

HTTP
import Injectable from '@angular/core';
import HttpClient, HttpHeaders from "@angular/common/http";
import Observable from "rxjs";
@Injectable(
  providedIn: 'root'
)
export class AppService 
constructor(private httpClient: HttpClient) 
  
hello(): Observable 
    const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
    return this.httpClient.get("http://localhost:8080/api/home",
      headers, responseType: 'text');
  

Now Let’s call it from our app.component.ts

App components
import Component, OnDestroy from '@angular/core';
import OAuthService from "angular-oauth2-oidc";
import authConfig from "./auth.config";
import AppService from "./app.service";
import Subscription from "rxjs";
@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
)
export class AppComponent implements OnDestroy
  title="frontend";
  text="";
  helloSubscription: Subscription
constructor(private oauthService: OAuthService, private appService: AppService) 
    this.configure();
    this.helloSubscription = appService.hello().subscribe(response => 
      this.text = response;
    );
  
ngOnDestroy(): void 
    this.helloSubscription.unsubscribe();
  
login() 
    this.oauthService.initCodeFlow();
  
private configure() 
    this.oauthService.configure(authConfig);
    this.oauthService.loadDiscoveryDocumentAndTryLogin(); // This method is trigger issuer uri
  
logout() 
    this.oauthService.logOut();
  

Last we need to bind with our html page

Login

Refresh the browser after login to view the text

text

Logout

After finishing all the configs let’s start our application. ‘npm start’

Spring-boot (Resource server configuration)

First I am adding 3 dependencies in the pom.xml

1.spring-boot-starter-oauth2-resource-server – which will enable the resource server capabilities inside our spring-boot application.

2. spring-security-oauth2-jose– Enables the Java-script object signing and Encryption Framework. Which is used to securely transfer claims between 2 parties. This means transferring the JWT (JSON Web Token), JWS (JSON Web Signatures), JWE (JSON Web Encryption), JWK (JSON Web Key).

3. spring-boot-starter-securityEnable spring security.

    org.springframework.boot
    spring-boot-starter-oauth2-resource-server


    org.springframework.security
    spring-security-oauth2-jose


    org.springframework.boot
    spring-boot-starter-security

Now configure the resource server properties

Spring boot |  Oauth 2 - demo realm
Keycloak
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8180/realms/oauth2-demo-realm/protocol/openid-connect/certs

OK, we configured the resource server.

Let’s create an endpoint.

I am creating the Controller “HomeRestController”, enabling the Rest api & cross-origin

package com.amitech.pkce.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/home")
@CrossOrigin(origins = "*")
public class HomeRestController 
@GetMapping
    @ResponseStatus(HttpStatus.OK)
    public String home() 
        return "Hello";
    

Last thing is to configure the spring security

For that, i am creating the config package and creating a class as SecurityConfig.

Keycloak |  security configure
package com.amitech.pkce.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter 
@Override
    public void configure(HttpSecurity httpSecurity) throws Exception 
        httpSecurity
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .cors()
                .and()
                .csrf()
                .disable()
                .oauth2ResourceServer()
                .jwt();
    

This class should extend the web security configure the adapter, in this way it will overwrite the spring-boot default security config. Inside this method, we can customize how spring security can behave.

First of all, I am going to make sure that all requests to our resource server should be authorized first. So i can do that by adding the authorizeRequest().anyRequest().authenticated()

Finally, we completed our front-end and back-end implementation. Let’s dive into the demo testing.

Demo

Navigate to the endpoint localhost:4200 (angular endpoint host)

demo

click on login , you will redirect to the keycloak login page

Keycloak

If you check the request parameters you can see the code_challenge & code challenge method

Once you entered the credentials

code challenge |  Keycloak

If you check the request parameters again you will see the code, angular use this code to make the post request to the token endpoint.

Keycloak

Code verifier — this value is used to verify the code challenge method.

Keycloak

The response contains an access token, refresh token and ID token

Keycloak

Conclusion to Keycloak

I hope you understood my article with a full flow explanation. Now we could see that we don’t need to share our credentials through the network calls. Also, you learned about PKCE authorization code flow, configuring with the front-end client (In our case angular application), configuring with the resource server (In our case Spring-boot application), and configuring with the authorization server (In our case keycloak server). Please continue reading my article to learn more about keycloak and the latest technology trends.

What we have learned so far:

  1. Understand the basics of PKCE grant flow.
  2. Configuring PKCE in the Keycloak admin console.
  3. Configuring PKCE in angular application.
  4. Configuring PKCE in the Spring-boot application.

The media shown in this article is not owned by Analytics Vidhya and is used at the Author’s discretion.



Source link

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button