Skip to content

Commit 45d6586

Browse files
Typescript migration (#16)
* Project migration to TypeScript --------- Co-authored-by: Julian Pollinger <julian@pollinger.dev>
1 parent 76d4dbd commit 45d6586

19 files changed

+355
-197
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
dist
2+
3+
.idea
4+
15
# Logs
26
logs
37
*.log
@@ -64,4 +68,4 @@ src/
6468

6569
*.pem
6670

67-
local
71+
local

README.md

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,31 @@ More about Keycloak: http://www.keycloak.org/
2424
## Using the keycloak-backend module
2525
### Configuration
2626
```js
27-
const keycloak = require('keycloak-backend')({
27+
const Keycloak = require('keycloak-backend').Keycloak
28+
const keycloak = new Keycloak({
2829
"realm": "realm-name",
2930
"keycloak_base_url": "https://keycloak.example.org",
3031
"client_id": "super-secure-client",
3132
"username": "user@example.org",
3233
"password": "passw0rd",
3334
"is_legacy_endpoint": false
34-
});
35+
})
3536
```
3637
> The `is_legacy_endpoint` configuration property should be TRUE for older Keycloak versions (under 18)
3738
39+
For TypeScript:
40+
```ts
41+
import { Keycloak } from "keycloak-backend"
42+
const keycloak = new Keycloak({
43+
"realm": "realm-name",
44+
"keycloak_base_url": "https://keycloak.example.org",
45+
"client_id": "super-secure-client",
46+
"username": "user@example.org",
47+
"password": "passw0rd",
48+
"is_legacy_endpoint": false
49+
})
50+
```
51+
3852
### Generating access tokens
3953
```js
4054
const accessToken = await keycloak.accessToken.get()
@@ -45,30 +59,33 @@ request.get('http://service.example.org/api/endpoint', {
4559
'auth': {
4660
'bearer': await keycloak.accessToken.get()
4761
}
48-
});
62+
})
4963
```
5064

5165
### Validating access tokens
5266
#### Online validation
5367
This method requires online connection to the Keycloak service to validate the access token. It is highly secure since it also check for possible token invalidation. The disadvantage is that a request to the Keycloak service happens on every validation:
5468
```js
55-
const token = await keycloak.jwt.verify(accessToken);
56-
//console.log(token.isExpired());
57-
//console.log(token.hasRealmRole('user'));
58-
//console.log(token.hasApplicationRole('app-client-name', 'some-role'));
69+
const token = await keycloak.jwt.verify(accessToken)
70+
//console.log(token.isExpired())
71+
//console.log(token.hasRealmRole('user'))
72+
//console.log(token.hasApplicationRole('app-client-name', 'some-role'))
5973
```
6074

6175
#### Offline validation
6276
This method perform offline JWT verification against the access token using the Keycloak Realm public key. Performance is higher compared to the online method, as a disadvantage no access token invalidation on Keycloak server is checked:
6377
```js
64-
const cert = fs.readFileSync('public_cert.pem');
65-
const token = await keycloak.jwt.verifyOffline(accessToken, cert);
66-
//console.log(token.isExpired());
67-
//console.log(token.hasRealmRole('user'));
68-
//console.log(token.hasApplicationRole('app-client-name', 'some-role'));
78+
const cert = fs.readFileSync('public_cert.pem')
79+
const token = await keycloak.jwt.verifyOffline(accessToken, cert)
80+
//console.log(token.isExpired())
81+
//console.log(token.hasRealmRole('user'))
82+
//console.log(token.hasApplicationRole('app-client-name', 'some-role'))
6983
```
7084

7185
## Breaking changes
86+
### v4
87+
- Codebase migrated from JavaScript to TypeScript. Many thanks to @neferin12
88+
7289
### v3
7390
- The `UserManager` class was dropped
7491
- The `auth-server-url` config property was changed to `keycloak_base_url`

example/all.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34
const fs = require('fs');
4-
55
(async () => {
66
try {
77
// current version of Keycloak requires the openid scope for accessing user info endpoint

example/refresh-token.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34

45
keycloak.accessToken.get().then(async (accessToken) => {
56
// refresh operation is performed automatically on `keycloak.accessToken.get`

example/retrieve-decode-token.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34

45
keycloak.accessToken.get().then(async (accessToken) => {
56
const token = keycloak.jwt.decode(accessToken)

example/user-info.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34

45
keycloak.accessToken.get('openid').then(async (accessToken) => {
56
const info = await keycloak.accessToken.info(accessToken)

example/verify-offline.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34
const fs = require('fs')
45

56
const cert = fs.readFileSync('./local/public_cert.pem')

example/verify-token.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const config = require('../local/config-example')
2-
const keycloak = require('../libs/index')(config)
2+
const Keycloak = require('../dist').Keycloak
3+
const keycloak = new Keycloak(config)
34

45
keycloak.accessToken.get('openid').then(async (accessToken) => {
56
const token = await keycloak.jwt.verify(accessToken)

libs/AccessToken.js

Lines changed: 0 additions & 81 deletions
This file was deleted.

libs/AccessToken.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { stringify } from 'querystring'
2+
import { IInternalConfig } from './index'
3+
import { AxiosInstance } from 'axios'
4+
5+
interface ICommonRequestOptions {
6+
grant_type: string
7+
client_id: string
8+
client_secret?: string
9+
}
10+
11+
interface IGetOptions extends ICommonRequestOptions {
12+
username?: string
13+
password?: string
14+
scope?: string
15+
}
16+
17+
interface IRefreshOptions extends ICommonRequestOptions {
18+
refresh_token: string
19+
}
20+
21+
export class AccessToken {
22+
private data: any
23+
24+
constructor (private readonly config: IInternalConfig, private readonly client: AxiosInstance) {
25+
}
26+
27+
async info (accessToken: string): Promise<any> {
28+
const endpoint = `${this.config.prefix}/realms/${this.config.realm}/protocol/openid-connect/userinfo`
29+
const response = await this.client.get(endpoint, {
30+
headers: {
31+
Authorization: 'Bearer ' + accessToken
32+
}
33+
})
34+
35+
return response.data
36+
}
37+
38+
async refresh (refreshToken: string): Promise<any> {
39+
const options: IRefreshOptions = {
40+
grant_type: 'refresh_token',
41+
client_id: this.config.client_id,
42+
refresh_token: refreshToken
43+
}
44+
if (this.config.client_secret != null) {
45+
options.client_secret = this.config.client_secret
46+
}
47+
48+
const endpoint = `${this.config.prefix}/realms/${this.config.realm}/protocol/openid-connect/token`
49+
return await this.client.post(endpoint, stringify({ ...options }))
50+
}
51+
52+
async get (scope?: string): Promise<string> {
53+
if (this.data == null) {
54+
const options: IGetOptions = {
55+
grant_type: 'password',
56+
username: this.config.username,
57+
password: this.config.password,
58+
client_id: this.config.client_id
59+
}
60+
if (this.config.client_secret != null) {
61+
options.client_secret = this.config.client_secret
62+
}
63+
if (scope != null) {
64+
options.scope = scope
65+
}
66+
67+
const endpoint = `${this.config.prefix}/realms/${this.config.realm}/protocol/openid-connect/token`
68+
const response = await this.client.post(endpoint, stringify({ ...options }))
69+
this.data = response.data
70+
71+
return this.data.access_token
72+
} else {
73+
try {
74+
await this.info(this.data.access_token)
75+
76+
return this.data.access_token
77+
} catch (err) {
78+
try {
79+
const response = await this.refresh(this.data.refresh_token)
80+
this.data = response.data
81+
82+
return this.data.access_token
83+
} catch (err) {
84+
delete this.data
85+
86+
return await this.get(scope)
87+
}
88+
}
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)