Using Keycloak in ReasonML

2018-09-18 • 2 min read
#reasonml#keycloak

Keycloak is an open source product to manage identity and access for modern applications and services. There is a client-side library (Keycloak-js) that can be used to make secured application.

Recently I have been trying to integrate keycloak-js into my reason app for the first time and searched online to find a more elegant way to cope with it. I got some ideas from these two posts: [1], [2]. Below are some of my learning notes.

I did similar thing like what the above two posts did to initialize keycloak in my app. myConfig should be your personal keycloak config json file.

type keycloak;
type init;
type initParam;
[@bs.module] external keycloak : string => keycloak = "keycloak-js";
[@bs.obj] external makeInitParam : (~onLoad: string, unit) => initParam = "";
[@bs.send] external init : (keycloak, initParam) => Js.Promise.t(bool) = "";
let myConfig = "./keycloakConfig.json";
let auth = myConfig |> keycloak |. init(makeInitParam(~onLoad="login-required", ())));
view raw Keycloak.re hosted with ❤ by GitHub

type state = {
isAuthed: bool
};
type action = | SetAuthenticated(bool);
let component = ReasonReact.reducerComponent("KeycloakApp");
let make = (_children) => {
...component,
initialState: () => {isAuthed: false},
reducer: (action, _state) =>
switch action {
| SetAuthenticated(isAuthed) => ReasonReact.Update({isAuthed})
},
didMount: ({send}) =>
Keycloak.auth
|> Js.Promise.then_(
(authenticated) => {
authenticated |. SetAuthenticated |> send;
Js.Promise.resolve()
}
)
|> ignore,
render: ({state}) =>
switch state.isAuthed {
| false => <div> ("You're not authorized." |> ReasonReact.str) </div>
| true => <MySecuredApp />
}
};
view raw KeycloakApp.re hosted with ❤ by GitHub

We can call keycloak when our reason app is mounted; therefore, we are able to check if the incoming user is authenticated to view the secured part of our app (ref). If all the other things are processed on the server side, then we’re done.

However, I think a better way to manage authentication is to integrate Keycloak on the server side (so that we won’t expose our access token or maybe other information on the client side). That’s said we have a backend that is ready to be integrated. All we need to do at frontend is to redirect users to login api when other endpoints response 401 status code. Everything will be as simple as the following code snippet:

type username = string;
type state = {userName: option(username)};
type action =
| SetUserName(option(username));
/* Webapi: https://github.com/reasonml-community/bs-webapi-incubator */
let redirectToLogin = () => {
let currentUrl = Webapi.Dom.location |> Webapi.Dom.Location.href |> Js.Global.encodeURIComponent;
let url = "https://myapi.net/login?redirect_url=" ++ currentUrl;
Webapi.Dom.location |. Webapi.Dom.Location.setHref(url)
};
let handleLoadUserName = (_payload, {ReasonReact.send}) =>
Js.Promise.(
Fetch.fetchWithInit(
"https://myapi.net/username",
Fetch.RequestInit.make(~credentials=Include, ())
)
|> then_(
(res) => {
let isOK = res |> Fetch.Response.ok;
if (isOK) {
res
|> Fetch.Response.json
|> Js.Promise.then_((r) => {Some(r |> NameJsonDecoder) |. SetUserName |> send; Js.Promise.resolve() } )
} else {
let status = res |> Fetch.Response.status;
let statusText = res |> Fetch.Response.statusText;
if (status == 401) {
redirectToLogin()
};
Js.Promise.resolve()
}
}
)
|> catch((err) => Js.log(err))
)
|> ignore;
let component = ReasonReact.reducerComponent("KeycloakAppV2");
let make = (_children) => {
...component,
initialState: () => {userName: None},
reducer: (action, state) =>
switch action {
| SetUserName(userName) => ReasonReact.Update({...state, userName})
},
didMount: ({handle}) => handle(handleLoadUserName, ()),
render: ({state}) =>
<div>
(
switch state.userName {
| Some(u) => <MaybeSomeSecuredComponents user=(u) />
| None => <div> ("You're not logged in" |> ReasonReact.str) </div>
}
)
</div>
};
view raw KeycloakAppV2.re hosted with ❤ by GitHub

We don’t need to use keycloak-js anymore. Everything is handled on the server side and we don’t need to ask authentication from keycloak from both frontend and backend. Note that if user does not have permission: e.g., not an admin, (instead of not logging in to keycloak), it’s better to handle 403 status code somewhere else.

This article is originally published on my Medium.