Skip to content

Commit af06975

Browse files
author
Nick Bragdon
committed
Adding support for using the refresh token to generate a new access token once expired
1 parent 32ebcd2 commit af06975

6 files changed

Lines changed: 50 additions & 5 deletions

File tree

server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"jet-logger": "^1.0.4",
7171
"jsonfile": "^6.1.0",
7272
"module-alias": "^2.2.2",
73+
"moment": "^2.29.1",
7374
"morgan": "^1.10.0"
7475
},
7576
"devDependencies": {

server/src/entities/AuthorizationToken.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import moment from "moment";
2+
13
export interface IAuthorizationToken {
24
access_token: string,
35
expires_in: number,
46
token_type: string,
57
scope: [string],
68
refresh_token: string,
79
patient: string,
8-
expires_at: number
10+
expires_at?: number
911
}
1012

1113
export default class AuthorizationToken implements IAuthorizationToken {
@@ -22,7 +24,7 @@ export default class AuthorizationToken implements IAuthorizationToken {
2224
constructor(authToken: IAuthorizationToken) {
2325
this.access_token = authToken.access_token;
2426
this.expires_in = authToken.expires_in;
25-
this.expires_at = authToken.expires_at;
27+
this.expires_at = authToken.expires_at ? authToken.expires_at : moment().add(this.expires_in).valueOf();
2628
this.patient = authToken.patient;
2729
this.refresh_token = authToken.refresh_token;
2830
this.scope = authToken.scope;

server/src/routes/Authorize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export async function authorizationCallback(req: Request, res: Response) {
1919
}
2020

2121
// this gets the token from Medicare.gov once the 'user' authenticates their Medicare.gov account
22-
const response = await getAccessToken(req.query.code?.toString(), req.query.state?.toString());
22+
const response = await getAccessToken(req.query.code?.toString(), req.query.state?.toString());
2323
const authToken = new AuthorizationToken(response.data);
2424

2525
/* DEVELOPER NOTES:

server/src/routes/Data.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import axios from 'axios';
33
import config from '../configs/config';
44
import db from '../utils/db';
55
import { getLoggedInUser } from 'src/utils/user';
6+
import moment from 'moment';
7+
import { refreshAccessToken } from 'src/utils/bb2';
68

79
/* DEVELOPER NOTES:
810
* This is our mocked Data Service layer for both the BB2 API
@@ -16,11 +18,23 @@ export async function getBenefitData(req: Request, res: Response) {
1618
const loggedInUser = getLoggedInUser(db);
1719
const envConfig = config[db.settings.env];
1820
const BB2_BENEFIT_URL = envConfig.bb2BaseUrl + '/' + db.settings.version + '/fhir/ExplanationOfBenefit/';
19-
21+
22+
if (!loggedInUser.authToken || !loggedInUser.authToken.access_token) {
23+
return {};
24+
}
25+
26+
/*
27+
* If the access token is expired, use the refresh token to generate a new one
28+
*/
29+
if (moment(loggedInUser.authToken.expires_at).isBefore(moment())) {
30+
const newAuthToken = await refreshAccessToken(loggedInUser.authToken.refresh_token)
31+
loggedInUser.authToken = newAuthToken;
32+
}
33+
2034
const response = await axios.get(BB2_BENEFIT_URL, {
2135
params: req.query,
2236
headers: {
23-
'Authorization': `Bearer ${loggedInUser.authToken?.access_token}`
37+
'Authorization': `Bearer ${loggedInUser.authToken.access_token}`
2438
}
2539
});
2640
return response.data;

server/src/utils/bb2.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import FormData from 'form-data';
44
import db from './db';
55
import config from '../configs/config';
66
import { generateCodeChallenge, generateRandomState } from './generatePKCE';
7+
import AuthorizationToken from '@entities/AuthorizationToken';
78

89
export function generateAuthorizeUrl(): string {
910
const envConfig = config[db.settings.env];
@@ -47,4 +48,26 @@ export async function getAccessToken(code: string, state: string | undefined) {
4748
form.append('code_challenge', codeChallenge.codeChallenge);
4849
}
4950
return await axios.post(BB2_ACCESS_TOKEN_URL, form, { headers: form.getHeaders() });
51+
}
52+
53+
export async function refreshAccessToken(refreshToken: string) {
54+
const envConfig = config[db.settings.env];
55+
56+
const BB2_ACCESS_TOKEN_URL = envConfig.bb2BaseUrl + '/' + db.settings.version + '/o/token/';
57+
58+
const tokenResponse = await axios({
59+
method: 'post',
60+
url: BB2_ACCESS_TOKEN_URL,
61+
auth: {
62+
username: envConfig.bb2ClientId,
63+
password: envConfig.bb2ClientSecret
64+
},
65+
params: {
66+
'grant_type': 'refresh_token',
67+
'client_id': envConfig.bb2ClientId,
68+
'refresh_token': refreshToken
69+
}
70+
});
71+
72+
return new AuthorizationToken(tokenResponse.data);
5073
}

server/yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,6 +1681,11 @@ module-alias@^2.2.2:
16811681
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
16821682
integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==
16831683

1684+
moment@^2.29.1:
1685+
version "2.29.1"
1686+
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
1687+
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
1688+
16841689
morgan@^1.10.0:
16851690
version "1.10.0"
16861691
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"

0 commit comments

Comments
 (0)