Skip to content

Commit

Permalink
feat: implemented awthentication-jwt refreshtoken services
Browse files Browse the repository at this point in the history
  • Loading branch information
madaky committed Aug 14, 2020
1 parent c863aae commit 1d99caf
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 275 deletions.
68 changes: 41 additions & 27 deletions extensions/authentication-jwt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,23 @@ login, then decorate endpoints with `@authentication('jwt')` to inject the
logged in user's profile.

This module contains an example application in the `fixtures` folder. It has a
controller with endpoints `/login` and `/whoAmI`.
controller with endpoints `/login`, `/refreshlogin`, `/refresh` and `/whoAmI`.

Before using the below snippet do not forget to inject below repositories and bindingings
in your controller's constructor

```ts
@inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
public userService: UserService<User, Credentials>,
@inject(SecurityBindings.USER, {optional: true})
private user: UserProfile,
@inject(UserServiceBindings.USER_REPOSITORY)
public userRepository: UserRepository,
@inject(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE)
public refreshService: RefreshTokenService,
```

The code snippet for login function:

Expand Down Expand Up @@ -174,41 +190,39 @@ The code snippet for whoAmI function:

### Endpoints with refresh token

To add refresh token mechanism in your app, you have to call the following
interceptor.
To add refresh token mechanism in your app, you can follow below example code at the
endpoint.

1. `refresh-token-generate` : to generate the refresh token and access token
1. `To generate refresh token` : to generate the refresh token and access token
when user logins to your app with provided credentials.

```ts
@intercept('refresh-token-generate')
async refreshLogin(
@requestBody(CredentialsRequestBody) credentials: Credentials,
): Promise<TokenObject> {
// ensure the user exists, and the password is correct
const user = await this.userService.verifyCredentials(credentials);
// convert a User object into a UserProfile object (reduced set of properties)
const userProfile: UserProfile = this.userService.convertToUserProfile(
user,
);
// create a JSON Web Token based on the user profile
const token = {
accessToken: await this.jwtService.generateToken(userProfile),
};
return token;
}
async refreshLogin(
@requestBody(CredentialsRequestBody) credentials: Credentials,
): Promise<TokenObject> {
// ensure the user exists, and the password is correct
const user = await this.userService.verifyCredentials(credentials);
// convert a User object into a UserProfile object (reduced set of properties)
const userProfile: UserProfile = this.userService.convertToUserProfile(
user,
);
const accessToken = await this.jwtService.generateToken(userProfile);
const tokens = await this.refreshService.generateToken(
userProfile,
accessToken,
);
return tokens;
}
```

2. `refresh-token-grant`: to generate the access token by the refresh token
2. `To refresh the token`: to generate the access token by the refresh token
obtained from the the last login endpoint.

```ts
@intercept('refresh-token-grant')
async refresh(
@requestBody(RefreshGrantRequestBody) refreshGrant: RefreshGrant,
): Promise<{token: string}> {
const token = '';
return {token};
): Promise<TokenObject> {
return this.refreshService.refreshToken(refreshGrant.refreshToken);
}
```

Expand Down Expand Up @@ -348,7 +362,7 @@ provide your own `User` model and repository.
// for jwt access token
this.bind(TokenServiceBindings.TOKEN_SECRET).to("<yourSecret>");
// for refresh token
this.bind(RefreshTokenInterceptorBindings.TOKEN_SECRET).to("<yourSecret>");
this.bind(RefreshTokenServiceBindings.TOKEN_SECRET).to("<yourSecret>");
```

2. To change token expiration. to learn more about expiration time here at
Expand All @@ -358,7 +372,7 @@ provide your own `User` model and repository.
// for jwt access token expiration
this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to("<Expiration Time in sec>");
// for refresh token expiration
this.bind(RefreshTokenInterceptorBindings.TOKEN_EXPIRES_IN).to("<Expiration Time in sec>");
this.bind(RefreshTokenServiceBindings.TOKEN_EXPIRES_IN).to("<Expiration Time in sec>");
```

## Future Work
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {UserServiceBindings} from '../..';
import {OPERATION_SECURITY_SPEC, SECURITY_SCHEME_SPEC} from '../../';
import {UserRepository} from '../../repositories';
import {TestApplication} from '../fixtures/application';
import {RefreshTokenBindings} from '../../keys';

describe('jwt authentication', () => {
let app: TestApplication;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import path from 'path';
import {
JWTAuthenticationComponent,
UserServiceBindings,
RefreshTokenBindings,
RefreshTokenServiceBindings,
} from '../../';
import {DbDataSource} from './datasources/db.datasource';
import {MySequence} from './sequence';
Expand All @@ -39,7 +39,7 @@ export class TestApplication extends BootMixin(
// Bind datasource
this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
//Bind datasource for refreshtoken table
this.dataSource(DbDataSource, RefreshTokenBindings.DATASOURCE_NAME);
this.dataSource(DbDataSource, RefreshTokenServiceBindings.DATASOURCE_NAME);

this.component(RestExplorerComponent);
this.projectRoot = __dirname;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
TokenService,
UserService,
} from '@loopback/authentication';
import {inject, intercept} from '@loopback/core';
import {inject} from '@loopback/core';
import {get, post, requestBody} from '@loopback/rest';
import {SecurityBindings, securityId, UserProfile} from '@loopback/security';
import {
Expand All @@ -18,11 +18,13 @@ import {
TokenObject,
RefreshGrantRequestBody,
RefreshGrant,
RefreshTokenServiceBindings,
} from '../../../';
import {model, property} from '@loopback/repository';
import {genSalt, hash} from 'bcryptjs';
import {UserRepository} from '../../../repositories';
import {Credentials} from '../../../services/user.service';
import {RefreshTokenService} from '../../../keys';

const CredentialsSchema = {
type: 'object',
Expand Down Expand Up @@ -66,6 +68,8 @@ export class UserController {
private user: UserProfile,
@inject(UserServiceBindings.USER_REPOSITORY)
public userRepository: UserRepository,
@inject(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE)
public refreshService: RefreshTokenService,
) {}

@post('/users/signup', {
Expand Down Expand Up @@ -172,7 +176,6 @@ export class UserController {
},
},
})
@intercept('refresh-token-generate')
async refreshLogin(
@requestBody(CredentialsRequestBody) credentials: Credentials,
): Promise<TokenObject> {
Expand All @@ -182,11 +185,12 @@ export class UserController {
const userProfile: UserProfile = this.userService.convertToUserProfile(
user,
);
// create a JSON Web Token based on the user profile
const token = {
accessToken: await this.jwtService.generateToken(userProfile),
};
return token;
const accessToken = await this.jwtService.generateToken(userProfile);
const tokens = await this.refreshService.generateToken(
userProfile,
accessToken,
);
return tokens;
}

@post('/refresh', {
Expand All @@ -198,8 +202,8 @@ export class UserController {
schema: {
type: 'object',
properties: {
refreshToken: {
type: 'string',
accessToken: {
type: 'object',
},
},
},
Expand All @@ -208,11 +212,9 @@ export class UserController {
},
},
})
@intercept('refresh-token-grant')
async refresh(
@requestBody(RefreshGrantRequestBody) refreshGrant: RefreshGrant,
): Promise<{token: string}> {
const token = '';
return {token};
): Promise<TokenObject> {
return this.refreshService.refreshToken(refreshGrant.refreshToken);
}
}
15 changes: 4 additions & 11 deletions extensions/authentication-jwt/src/__tests__/unit/jwt.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {securityId} from '@loopback/security';
import {expect} from '@loopback/testlab';
import {JWTService} from '../../';

Expand All @@ -19,17 +19,11 @@ describe('token service', () => {
id: '1',
name: 'test',
};
type Setter<T> = (value?: T) => void;

const TOKEN_SECRET_VALUE = 'myjwts3cr3t';
const TOKEN_EXPIRES_IN_VALUE = '60';
const setCurrentUser: Setter<UserProfile> = userProfile => {
return userProfile;
};
const jwtService = new JWTService(
TOKEN_SECRET_VALUE,
TOKEN_EXPIRES_IN_VALUE,
setCurrentUser,
);

const jwtService = new JWTService(TOKEN_SECRET_VALUE, TOKEN_EXPIRES_IN_VALUE);

it('token service generateToken() succeeds', async () => {
const token = await jwtService.generateToken(USER_PROFILE);
Expand All @@ -39,7 +33,6 @@ describe('token service', () => {
it('token service verifyToken() succeeds', async () => {
const token = await jwtService.generateToken(USER_PROFILE);
const userProfileFromToken = await jwtService.verifyToken(token);

expect(userProfileFromToken).to.deepEqual(DECODED_USER_PROFILE);
});

Expand Down
3 changes: 0 additions & 3 deletions extensions/authentication-jwt/src/interceptors/index.ts

This file was deleted.

This file was deleted.

Loading

0 comments on commit 1d99caf

Please sign in to comment.