Secure the Angular2 frontend service
This article is based on this article. To add Keycloak security to an Angular2 application we've to install the JavaScript Keycloak adapter.
npm install keycloak-js --save
Now we've to intercept every call to the frontend. We can achieve that in the
main.browser.ts
file:
import * as Keycloak from "keycloak-js";
let keycloak = Keycloak('keycloak/keycloak.json');
window['_keycloak'] = keycloak;
window['_keycloak'].init(
{onLoad: 'login-required'}
)
.success(function (authenticated) {
if (!authenticated) {
window.location.reload();
}
// refresh login
setInterval(function () {
keycloak.updateToken(70).success(function (refreshed) {
if (refreshed) {
console.log('Token refreshed');
} else {
console.log('Token not refreshed, valid for '
+ Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
}
}).error(function () {
console.error('Failed to refresh token');
});
}, 60000);
console.log("Loading...");
platformBrowserDynamic().bootstrapModule(AppModule);
});
Then we've to create a folder src/keycloak
with the file keycloak.json
and
the following content:
{
"realm": "battleapp-local",
"auth-server-url": "http://localhost:8280/auth",
"ssl-required": "none",
"resource": "battleapp-frontend",
"public-client": true
}
To include Keycloak in our services we can use the following library:
npm install angular2-jwt --save
Then we can configure the auth provider in app.module.ts
:
...
import {provideAuth} from "angular2-jwt";
...
providers: [ // expose our Services and Providers into Angular's dependency injection
ENV_PROVIDERS,
APP_PROVIDERS,
provideAuth({
globalHeaders: [{'Content-Type': 'application/json'}],
noJwtError: true,
tokenGetter: () => {
return window['_keycloak'].token;
}
})
]
...
Now we can use the AuthHttp
in our users.service.ts
class.
import {Injectable} from "@angular/core";
import {Response, Http} from "@angular/http";
import {Observable} from "rxjs";
import {User} from "./user";
import {AuthHttp} from "angular2-jwt";
@Injectable()
export class UserService {
private environment: Observable<any>;
constructor(private authHttp: AuthHttp, private http: Http) {
this.environment = this.http
.get('/environment/environment.json')
.map(res => res.json());
}
public getUsersUrl(env: any): string {
let baseUsersUrl = '/battleapp/';
let api: string = 'resources/';
let usersUrl = 'http://' + env.host + ':' + env.port + baseUsersUrl + api + 'users/';
return usersUrl;
}
public getAll = (): Observable<User[]> => {
return this.environment.flatMap((env: any) => {
return this.authHttp
.get(this.getUsersUrl(env))
.map(res => res.json());
});
};
public search = (nickname: string): Observable<User[]> => {
return this.environment.flatMap((env: any) => {
return this.authHttp
.get(this.getUsersUrl(env) + "?nickname=" + nickname)
.map(res => res.json());
});
};
public find = (id: number): Observable<User> => {
return this.environment.flatMap((env: any) => {
return this.authHttp
.get(this.getUsersUrl(env) + id)
.map(res => res.json());
});
};
public create = (firstName: string, lastName: string): Observable<User> => {
return this.environment.flatMap((env: any) => {
var toAdd = JSON.stringify({firstName: firstName, lastName: lastName});
return this.authHttp
.post(this.getUsersUrl(env), toAdd)
.map(res => res.json());
});
};
public update = (id: number, itemToUpdate: User): Observable<User> => {
return this.environment.flatMap((env: any) => {
return this.authHttp
.put(this.getUsersUrl(env) + id, JSON.stringify(itemToUpdate))
.map(res => res.json());
});
};
public delete = (id: number): Observable<Response> => {
return this.environment.flatMap((env: any) => {
return this.authHttp
.delete(this.getUsersUrl(env) + id);
});
};
}
Create test environment with security
In the start.js
script form the start test environment script we've to
add keycloak.json
with a ConfigMap. Therefore we've to create a new file
keycloak.json
in the battleapp-frontend
and battleapp-frontend-test
folder with the corresponding content:
{
"realm": "battleapp",
"auth-server-url": "https://disruptor.ninja:30182/auth",
"ssl-required": "none",
"resource": "battleapp-frontend",
"public-client": true
}
{
"realm": "battleapp-test",
"auth-server-url": "https://disruptor.ninja:30182/auth",
"ssl-required": "none",
"resource": "battleapp-frontend",
"public-client": true
}
Now we've to delete the existing ConfigMaps and recreate them:
kc delete configmap battleapp-frontend
kc delete configmap battleapp-frontend-test
kc create configmap battleapp-frontend-test --from-file=battleapp-frontend-test
kc create configmap battleapp-frontend --from-file=battleapp-frontend
And test it:
kc get configmap battleapp-frontend-test -o yaml
apiVersion: v1
data:
environment.json: |
{
"host": "disruptor.ninja",
"port": 31080
}
keycloak.json: |
{
"realm": "battleapp-test",
"auth-server-url": "https://disruptor.ninja:30182/auth",
"ssl-required": "none",
"resource": "battleapp-frontend",
"public-client": true
}
kind: ConfigMap
metadata:
creationTimestamp: 2016-12-31T15:54:44Z
name: battleapp-frontend-test
namespace: default
resourceVersion: "1049486"
selfLink: /api/v1/namespaces/default/configmaps/battleapp-frontend-test
uid: 72a5399b-cf71-11e6-a836-0050563cad2a
Now we can add the ConfigMap in the start.js
script form the start
test environment script:
...
dfw.write(" - name: keycloak\n");
dfw.write(" mountPath: /usr/share/nginx/html/keycloak\n");
...
dfw.write(" - name: keycloak\n");
dfw.write(" configMap:\n");
dfw.write(" name: battleapp-frontend-test\n");
dfw.write(" items:\n");
dfw.write(" - key: keycloak.json\n");
dfw.write(" path: keycloak.json\n");
...
And in the start.js
script from the canary release:
...
dfw.write(" - name: keycloak\n");
dfw.write(" mountPath: /usr/share/nginx/html/keycloak\n");
...
dfw.write(" - name: keycloak\n");
dfw.write(" configMap:\n");
dfw.write(" name: battleapp-frontend\n");
dfw.write(" items:\n");
dfw.write(" - key: keycloak.json\n");
dfw.write(" path: keycloak.json\n");
...