New KOBI documentation is available: https://adeo-ccdp.gitbook.io/kobi/

What KOBI does not

KOBI does not come with an authentication solution.

The first advantage of this choice is that it allow you to keep your existing authentication mecanism (in case of a legacy), or choose whatever you want to use for your new website.

The second is that KOBI is not bound to your information system (identity provider, user databases…).

What KOBI does

  • Propagate the authentication information to your applications through a JWT cookie.
  • Redirect to the login page if needed
  • Prevent authentication forgery (JWT signature verification)
  • Handle refresh

Constraints

To do all that the solution comes with some constraints :

  • YOU HAVE TO be using a JWT cookie to store authentication information (user infos and roles, tokens…)
  • YOU HAVE TO provide the application that produce this cookie
  • YOU HAVE TO generate a pair of RSA keys to sign the JWT
  • YOU CAN provide a refresh endpoint

Setup

Key pair generation

https://rietta.com/blog/openssl-generating-rsa-key-from-command/

For now, KOBI handles only RSA256 algorithm.

Site configuration

From Designer, when configuring your site, under “security” you will find this kind of form :

  • cookie name : your authentication cookie name (ex : auth.jwt)
  • cookie domain : your site domain (ex : .my-domain.com)
  • http-only : true is highly recommanded
  • secured : true is highly recommanded
  • issuer : for Oauth, should be your client_id
  • public-key : the generated RSA public key
  • handle-refresh : if true, set the refresh-uri
  • refresh-uri : your authentication application path to refresh authentication (ex : https://my-auth-app/refresh)
  • login-path : the path to the login on your website (ex : /login)

Authentication application

You might want to ask for the KOBI team wether you should use fragments or applicative mode and Pages or Page Models, depends on your workflows, existing legacy…

Let’s keep it simple, we will create an application using the applicative mode that display a login form (use the according fragment documentation) and redirect to a callback with the authentication cookie set.

This applicative context should be set in a Page Model.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Controller
@RequestMapping
@RequiredArgsConstructor
public class LoginController {

	private final RequestSavingService requestSavingService;
	private final OauthService oauthService;
	private final JWTService jwtService;
	private final AuthenticationsConfig authenticationsConfig;

	@ApplicationContext("lmfr-authentication") // Application context to embed the app in a Header/Footer Model
	@GetMapping("/login")
	public Mono<String> getLoginApp(@RequestParam(name = "callback") String callback,
		ServerWebExchange serverWebExchange, Model model){

		//Save the request in a temporary cookie, returns a random generated state 
		return this.requestSavingService
				.saveRequest(serverWebExchange, "MY_CLIENT_ID", callback, "https://this-auth-app/callback")
				.flatMap(state -> this.oauthService.getOauthLoginUri(state,"MY_CLIENT_ID", "https://this-auth-app/callback"))
				.flatMap(oauthUri -> {
						//add oauth uri to template model
						model.addAttribute("src", oauthUri);
						
						// We do the redirection in Javascript client-side, in order to set the cookie (request save)
						return Mono.just("/components/login/login-redirect");
				});
	}

	//Come back from Oauth with a code, get an access token, create the JWT cookie and return to the callback
	@GetMapping("/callback")
	public Mono<String> getCallback(@RequestParam(name = "code") String code,
	  	@RequestParam(name = "state") String state,
	  	Model model, ServerWebExchange serverWebExchange) {
		//Retrieve our saved request from cookies
		return this.requestSavingService.getSavedRequest(serverWebExchange).flatMap(savedRequest -> {

			//Check Oauth returned our code & state and verify state against the one we saved initialy
			if (state == null || !state.equals(savedRequest.getState())) {
				return Mono.error(new AccessDeniedException());
			}

			//Exchange the code for an AT, fetch user-infos and create the JWT token
			return oauthService.getAccessToken(code, savedRequest.getProvider(), savedRequest.getRedirectUri())
					.map(accessToken -> this.oauthService.getUserInfos(accessToken.getIdToken(), savedRequest.getProvider()))
					.flatMap(userInfos -> this.jwtService.createJwtToken(userInfos))
					.flatMap(jwt -> {
						//Add the jwt cookie in the response
						serverWebExchange.getResponse().addCookie(ResponseCookie.from("auth.jwt", jwt).build());
						//Delete the saved request cookie
						this.requestSavingService.deleteSavedRequest(serverWebExchange, savedRequest.getProvider());

						return Mono.just("redirect:" + savedRequest.getCallback());
					});
		});
	}
}

For the JWT creation feel free to use your own implementation or existing library, else you can use the kobi-starter “KobiUser” class (JAVA) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
KobiUser user = new KobiUser.KobiUserBuilder("MY_CLIENT_ID", "Kobi", expireDate)
				.withAccessToken(accessToken)
				.withRefreshToken(refreshToken)
				.withIdToken(idToken)
				.withUserId(userId)
				.withAttributes(attributes) // Map of user info
				.withRoles(roles)
				.build();

String jwtToken = user.getJwtToken(this.algorithm, this.objectMapper));

Workflow

If your component does not receive the auth.jwt in its request, and requires to be authentication, it can respond with a 401.

When Gluer receive a 401 from a primary fragment or applicative mode it will redirect to your login page with a callback parameter.

Auth redirect Auth redirect

Once logged in, Gluer will check the auth.jwt cookie (signature and validity date), if OK it will be sent to each components.
If the JWT is expired, Gluer will call the refresh-uri (if enabled) with all the cookies from the incoming request and set back the cookie from the response to the component’s request and final client response.
If the refresh is disabled or the JWT signature is invalid, the cookie will be deleted.

Auth jwt Auth jwt

Kinematic