Skip to content

Commit

Permalink
feat: create jwt auth service
Browse files Browse the repository at this point in the history
Signed-off-by: jannyHou <[email protected]>
  • Loading branch information
jannyHou committed Feb 11, 2019
1 parent 81f552a commit d96d2e9
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 140 deletions.
44 changes: 13 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ import {
AuthenticationBindings,
AuthenticationComponent,
} from '@loopback/authentication';
import {JWTAuthenticationBindings, OtherServicesBindings} from './keys';
import {StrategyResolverProvider} from './providers/strategy.resolver.provider';
import {AuthenticateActionProvider} from './providers/custom.authentication.provider';
import {
JWTAuthenticationService,
JWT_SECRET,
} from './services/JWT.authentication.service';
import {hashPassword} from './services/hash.password.bcryptjs';
import {JWTStrategy} from './authentication-strategies/JWT.strategy';

/**
* Information from package.json
Expand All @@ -38,6 +45,7 @@ export class ShoppingApplication extends BootMixin(
// Bind package.json to the application context
this.bind(PackageKey).to(pkg);

// Bind authentication component related elements
this.component(AuthenticationComponent);
this.bind(AuthenticationBindings.AUTH_ACTION).toProvider(
AuthenticateActionProvider,
Expand All @@ -46,6 +54,16 @@ export class ShoppingApplication extends BootMixin(
StrategyResolverProvider,
);

// Bind JWT authentication strategy related elements
this.bind(JWTAuthenticationBindings.STRATEGY).toClass(JWTStrategy);
this.bind(JWTAuthenticationBindings.SECRET).to(JWT_SECRET);
this.bind(JWTAuthenticationBindings.SERVICE).toClass(
JWTAuthenticationService,
);

// Bind other services
this.bind(OtherServicesBindings.HASH_PASSWORD).to(hashPassword);

// Set up the custom sequence
this.sequence(MySequence);

Expand Down
16 changes: 12 additions & 4 deletions src/authentication-strategies/JWT.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

// Consider turn it to a binding
const SECRET = 'secretforjwt';
import {JWTAuthenticationBindings} from '../keys';
import {Request, HttpErrors} from '@loopback/rest';
import {UserProfile} from '@loopback/authentication';
import {AuthenticationStrategy} from './authentication.strategy';
import {decodeAccessToken} from '../utils/user.authentication';
import {inject} from '@loopback/core';
import {JWTAuthenticationService} from '../services/JWT.authentication.service';

export class JWTStrategy implements AuthenticationStrategy {
constructor(
@inject(JWTAuthenticationBindings.SERVICE)
public jwt_authentication_service: JWTAuthenticationService,
@inject(JWTAuthenticationBindings.SECRET)
public jwt_secret: string,
) {}
async authenticate(request: Request): Promise<UserProfile | undefined> {
let token = request.query.access_token || request.headers['authorization'];
if (!token) throw new HttpErrors.Unauthorized('No access token found!');
Expand All @@ -20,7 +26,9 @@ export class JWTStrategy implements AuthenticationStrategy {
}

try {
const user = await decodeAccessToken(token, SECRET);
const user = await this.jwt_authentication_service.decodeAccessToken(
token,
);
return user;
} catch (err) {
Object.assign(err, {
Expand Down
19 changes: 12 additions & 7 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import {
AuthenticationBindings,
} from '@loopback/authentication';
import {Credentials} from '../repositories/user.repository';
import {
validateCredentials,
getAccessTokenForUser,
hashPassword,
} from '../utils/user.authentication';
import {HashPassword} from '../services/hash.password.bcryptjs';
import {JWTAuthenticationService} from '../services/JWT.authentication.service';
import {JWTAuthenticationBindings, OtherServicesBindings} from '../keys';
import {validateCredentials} from '../services/JWT.authentication.service';
import * as _ from 'lodash';

// TODO(jannyHou): This should be moved to @loopback/authentication
Expand All @@ -40,12 +39,16 @@ export class UserController {
public recommender: RecommenderService,
@inject.setter(AuthenticationBindings.CURRENT_USER)
public setCurrentUser: Setter<UserProfile>,
@inject(OtherServicesBindings.HASH_PASSWORD)
public hashPassword: HashPassword,
@inject(JWTAuthenticationBindings.SERVICE)
public jwt_authentication_service: JWTAuthenticationService,
) {}

@post('/users')
async create(@requestBody() user: User): Promise<User> {
validateCredentials(_.pick(user, ['email', 'password']));
user.password = await hashPassword(user.password, 10);
user.password = await this.hashPassword(user.password, 10);

// Save & Return Result
const savedUser = await this.userRepository.create(user);
Expand Down Expand Up @@ -143,7 +146,9 @@ export class UserController {
@requestBody() credentials: Credentials,
): Promise<{token: string}> {
validateCredentials(credentials);
const token = await getAccessTokenForUser(this.userRepository, credentials);
const token = await this.jwt_authentication_service.getAccessTokenForUser(
credentials,
);
return {token};
}
}
22 changes: 22 additions & 0 deletions src/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {BindingKey} from '@loopback/context';
import {JWTAuthenticationService} from './services/JWT.authentication.service';
import {HashPassword} from './services/hash.password.bcryptjs';
import {JWTStrategy} from './authentication-strategies/JWT.strategy';

// Discussion point for reviewers:
// What would be the good naming conversion for bindings?
export namespace JWTAuthenticationBindings {
export const STRATEGY = BindingKey.create<JWTStrategy>(
'authentication.strategies.jwt.strategy',
);
export const SECRET = BindingKey.create<string>('authentication.jwt.secret');
export const SERVICE = BindingKey.create<JWTAuthenticationService>(
'services.authentication.jwt.service',
);
}

export namespace OtherServicesBindings {
export const HASH_PASSWORD = BindingKey.create<HashPassword>(
'services.hash_password',
);
}
5 changes: 4 additions & 1 deletion src/providers/strategy.resolver.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import {
AuthenticationMetadata,
} from '@loopback/authentication';
import {JWTStrategy} from '../authentication-strategies/JWT.strategy';
import {JWTAuthenticationBindings} from '../keys';
export class StrategyResolverProvider
implements Provider<JWTStrategy | undefined> {
constructor(
@inject(AuthenticationBindings.METADATA)
private metadata: AuthenticationMetadata,
@inject(JWTAuthenticationBindings.STRATEGY)
private jwt_strategy: JWTStrategy,
) {}
value(): ValueOrPromise<JWTStrategy | undefined> {
if (!this.metadata) {
Expand All @@ -24,7 +27,7 @@ export class StrategyResolverProvider
const name = this.metadata.strategy;
// This should be extensible
if (name === 'jwt') {
return new JWTStrategy();
return this.jwt_strategy;
} else {
throw new Error(`The strategy ${name} is not available.`);
}
Expand Down
Loading

0 comments on commit d96d2e9

Please sign in to comment.