Loopback4-Authentication - NPM
Loopback4-Authentication - NPM
Loopback4-Authentication - NPM
node >=8.9
Dependencies up to date
@loopback/core ^2.16.0
@loopback/build ^6.4.0
npm vulnerabilities 0
npm v4.6.0
License MIT
Downloads 1.8k/week
Total Downloads 56k
You can use one or more strategies of the above in your application. For each of the
strategy (only which you use), you just need to provide your own verifier function,
making it easily configurable. Rest of the strategy implementation intricacies is
handled by extension.
Install
Quick Starter
For a quick starter guide, you can refer to our loopback 4 starter application which
utilizes all of the above auth strategies from the extension in a simple multi-tenant
application. Refer to the auth module there for specific details on authentication.
Detailed Usage
The first and common step for all of the startegies is to add the component to the
application. See below
// application.ts
ServiceMixin(RepositoryMixin(RestApplication)),
) {
super(options);
this.sequence(MySequence);
this.component(AuthenticationComponent);
Once this is done, you are ready to configure any of the available strategy in the
application.
Oauth2-client-password
First, create an AuthClient model implementing the IAuthClient interface. The purpose
of this model is to store oauth registered clients for the app in the DB. See sample
below.
@model({
name: 'auth_clients',
})
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'client_id',
})
clientId: string;
@property({
type: 'string',
required: true,
name: 'client_secret',
})
clientSecret: string;
@property({
type: 'array',
itemType: 'number',
name: 'user_ids',
})
userIds: number[];
constructor(data?: Partial<AuthClient>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same
strategy. You can add your application specific business logic for client auth here.
Here is simple example.
implements Provider<VerifyFunction.OauthClientPasswordFn> {
constructor(
@repository(AuthClientRepository)
) {}
value(): VerifyFunction.OauthClientPasswordFn {
return this.authClientRepository.findOne({
where: {
clientId,
clientSecret,
},
});
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.OAUTH2_CLIENT_PASSWORD_VERIFIER).
ClientPasswordVerifyProvider,
);
constructor(
@inject(AuthenticationBindings.CLIENT_AUTH_ACTION)
try {
await this.authenticateRequestClient(request);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD, {
passReqToCallback: true
})
@post('/auth/login', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: Object,
},
},
},
})
async login(
@requestBody()
req: LoginRequest,
): Promise<{
code: string;
}> {
....
}
For accessing the authenticated AuthClient model reference, you can inject the
CURRENT_CLIENT provider, provided by the extension, which is populated by the
auth action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_CLIENT)
Http-bearer
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is
simple example for JWT tokens.
implements Provider<VerifyFunction.BearerFn> {
constructor(
@repository(RevokedTokenRepository)
) {}
value(): VerifyFunction.BearerFn {
}) as User;
return user;
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.BEARER_TOKEN_VERIFIER).toProvider
BearerTokenVerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticate(STRATEGY.BEARER)
@get('/users', {
responses: {
'200': {
content: {
'application/json': {
},
},
},
},
})
async find(
}
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
local
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
implements Provider<VerifyFunction.LocalPasswordFn> {
constructor(
@repository(UserRepository)
) {}
value(): VerifyFunction.LocalPasswordFn {
try {
await this.userRepository.verifyPassword(username, pa
);
return user;
} catch (error) {
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.LOCAL_PASSWORD_VERIFIER).toProvid
LocalPasswordVerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticate(STRATEGY.LOCAL)
@post('/auth/login', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: Object,
},
},
},
})
async login(
@requestBody()
req: LoginRequest,
): Promise<{
code: string;
}> {
......
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
Oauth2-resource-owner-password
First, create an AuthClient model implementing the IAuthClient interface. The purpose
of this model is to store oauth registered clients for the app in the DB. See sample
below.
@model({
name: 'auth_clients',
})
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'client_id',
})
clientId: string;
@property({
type: 'string',
required: true,
name: 'client_secret',
})
clientSecret: string;
@property({
type: 'array',
itemType: 'number',
name: 'user_ids',
})
userIds: number[];
constructor(data?: Partial<AuthClient>) {
super(data);
Next, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for both of the above models. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
implements Provider<VerifyFunction.ResourceOwnerPasswordFn> {
constructor(
@repository(UserRepository)
@repository(AuthClientRepository)
) {}
value(): VerifyFunction.ResourceOwnerPasswordFn {
where: {
clientId,
},
});
AuthErrorKeys.ClientVerificationFailed,
);
return {
client,
user,
};
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.RESOURCE_OWNER_PASSWORD_VERIFIER)
ResourceOwnerVerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticate(STRATEGY.OAUTH2_RESOURCE_OWNER_GRANT)
@post('/auth/login-token', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginWithClientUser(
): Promise<TokenResponse> {
......
For accessing the authenticated AuthUser and AuthClient model reference, you can
inject the CURRENT_USER and CURRENT_CLIENT provider, provided by the
extension, which is populated by the auth action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
@inject.getter(AuthenticationBindings.CURRENT_CLIENT)
Google Oauth 2
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
required: true,
name: 'auth_provider',
})
authProvider: string;
@property({
type: 'string',
name: 'auth_id',
})
authId?: string;
@property({
type: 'string',
name: 'auth_token',
})
authToken?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
implements Provider<VerifyFunction.GoogleAuthFn> {
constructor(
@repository(UserRepository)
@repository(UserCredentialsRepository)
) {}
value(): VerifyFunction.GoogleAuthFn {
where: {
/* eslint-disable-next-line @typescript-eslint/no-exp
email: (profile as any)._json.email,
},
});
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.Invalid
}
if (
!user ||
) {
authUser.permissions = [];
authUser.externalAuthToken = accessToken;
authUser.externalRefreshToken = refreshToken;
return authUser;
};
}
}
Please note the Verify function type VerifyFunction.LocalPasswordFn
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.GOOGLE_OAUTH2_VERIFIER).toProvide
GoogleOauth2VerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
response,
);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@authenticate(
STRATEGY.GOOGLE_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.GOOGLE_AUTH_URL,
callbackURL: process.env.GOOGLE_AUTH_CALLBACK_URL,
clientID: process.env.GOOGLE_AUTH_CLIENT_ID,
clientSecret: process.env.GOOGLE_AUTH_CLIENT_SECRET,
tokenURL: process.env.GOOGLE_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/google', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginViaGoogle(
@param.query.string('client_id')
clientId?: string,
@param.query.string('client_secret')
clientSecret?: string,
): Promise<void> {}
@authenticate(
STRATEGY.GOOGLE_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.GOOGLE_AUTH_URL,
callbackURL: process.env.GOOGLE_AUTH_CALLBACK_URL,
clientID: process.env.GOOGLE_AUTH_CLIENT_ID,
clientSecret: process.env.GOOGLE_AUTH_CLIENT_SECRET,
tokenURL: process.env.GOOGLE_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/google-auth-redirect', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async googleCallback(
): Promise<void> {
where: {
clientId: clientId,
},
});
if (!client || !client.redirectUrl) {
try {
clientId,
user: this.user,
};
expiresIn: client.authCodeExpiration,
audience: clientId,
subject: this.user.username,
issuer: process.env.JWT_ISSUER,
});
response.redirect(`${client.redirectUrl}?code=${token}`);
} catch (error) {
Please note above that we are creating two new APIs for google auth. The first one is
for UI clients to hit. We are authenticating client as well, then passing the details to
the google auth. Then, the actual authentication is done by google authorization url,
which redirects to the second API we created after success. The first API method
body is empty as we do not need to handle its response. The google auth provider in
this package will do the redirection for you automatically.
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
Instagram Oauth 2
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
required: true,
name: 'auth_provider',
})
authProvider: string;
@property({
type: 'string',
name: 'auth_id',
})
authId?: string;
@property({
type: 'string',
name: 'auth_token',
})
authToken?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
import {Provider} from '@loopback/context';
implements Provider<VerifyFunction.InstagramAuthFn> {
constructor(
@repository(UserRepository)
@repository(UserCredentialsRepository)
) {}
value(): VerifyFunction.InstagramAuthFn {
where: {
/* eslint-disable-next-line @typescript-eslint/no-exp
email: (profile as any)._json.email,
},
});
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.Invalid
}
if (
!user ||
) {
authUser.permissions = [];
authUser.externalAuthToken = accessToken;
authUser.externalRefreshToken = refreshToken;
return authUser;
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.INSTAGRAM_OAUTH2_VERIFIER).toProv
InstagramOauth2VerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
response,
);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@authenticate(
STRATEGY.INSTAGRAM_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.INSTAGRAM_AUTH_URL,
callbackURL: process.env.INSTAGRAM_AUTH_CALLBACK_URL,
clientID: process.env.INSTAGRAM_AUTH_CLIENT_ID,
clientSecret: process.env.INSTAGRAM_AUTH_CLIENT_SECRET,
tokenURL: process.env.INSTAGRAM_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/instagram', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginViaInstagram(
@param.query.string('client_id')
clientId?: string,
@param.query.string('client_secret')
clientSecret?: string,
): Promise<void> {}
@authenticate(
STRATEGY.INSTAGRAM_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.INSTAGRAM_AUTH_URL,
callbackURL: process.env.INSTAGRAM_AUTH_CALLBACK_URL,
clientID: process.env.INSTAGRAM_AUTH_CLIENT_ID,
clientSecret: process.env.INSTAGRAM_AUTH_CLIENT_SECRET,
tokenURL: process.env.INSTAGRAM_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/instagram-auth-redirect', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async instagramCallback(
): Promise<void> {
where: {
clientId: clientId,
},
});
if (!client || !client.redirectUrl) {
try {
clientId,
user: this.user,
};
expiresIn: client.authCodeExpiration,
audience: clientId,
subject: this.user.username,
issuer: process.env.JWT_ISSUER,
});
response.redirect(`${client.redirectUrl}?code=${token}`);
} catch (error) {
Please note above that we are creating two new APIs for instagram auth. The first
one is for UI clients to hit. We are authenticating client as well, then passing the
details to the instagram auth. Then, the actual authentication is done by instagram
authorization url, which redirects to the second API we created after success. The first
API method body is empty as we do not need to handle its response. The instagram
auth provider in this package will do the redirection for you automatically.
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
Apple Oauth 2
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
required: true,
name: 'auth_provider',
})
authProvider: string;
@property({
type: 'string',
name: 'auth_id',
})
authId?: string;
@property({
type: 'string',
name: 'auth_token',
})
authToken?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
}
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
implements Provider<VerifyFunction.AppleAuthFn> {
constructor(
@repository(UserRepository)
@repository(UserCredentialsRepository)
) {}
value(): VerifyFunction.AppleAuthFn {
where: {
/* eslint-disable-next-line @typescript-eslint/no-exp
email: (profile as any)._json.email,
},
});
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.Invalid
}
if (
!user ||
) {
authUser.permissions = [];
authUser.externalAuthToken = accessToken;
authUser.externalRefreshToken = refreshToken;
return authUser;
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.APPLE_OAUTH2_VERIFIER).toProvider
AppleOauth2VerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
response,
);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@authenticate(
STRATEGY.APPLE_OAUTH2,
accessType: 'offline',
callbackURL: process.env.APPLE_AUTH_CALLBACK_URL,
clientID: process.env.APPLE_AUTH_CLIENT_ID,
teamID: process.env.APPLE_AUTH_TEAM_ID,
keyID: process.env.APPLE_AUTH_KEY_ID,
privateKeyLocation: process.env.APPLE_AUTH_PRIVATE_KEY_LO
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/oauth-apple', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginViaApple(
@param.query.string('client_id')
clientId?: string,
@param.query.string('client_secret')
clientSecret?: string,
): Promise<void> {}
@authenticate(
STRATEGY.APPLE_OAUTH2,
accessType: 'offline',
callbackURL: process.env.APPLE_AUTH_CALLBACK_URL,
clientID: process.env.APPLE_AUTH_CLIENT_ID,
teamID: process.env.APPLE_AUTH_TEAM_ID,
keyID: process.env.APPLE_AUTH_KEY_ID,
privateKeyLocation: process.env.APPLE_AUTH_PRIVATE_KEY_LO
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/apple-oauth-redirect', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async appleCallback(
): Promise<void> {
where: {
clientId: clientId,
},
});
if (!client || !client.redirectUrl) {
try {
clientId,
user: this.user,
};
expiresIn: client.authCodeExpiration,
audience: clientId,
subject: this.user.username,
issuer: process.env.JWT_ISSUER,
});
response.redirect(`${client.redirectUrl}?code=${token}`);
} catch (error) {
Please note above that we are creating two new APIs for apple auth. The first one is
for UI clients to hit. We are authenticating client as well, then passing the details to
the apple auth. Then, the actual authentication is done by apple authorization url,
which redirects to the second API we created after success. The first API method
body is empty as we do not need to handle its response. The apple auth provider in
this package will do the redirection for you automatically.
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
Facebook Oauth 2
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
required: true,
name: 'auth_provider',
})
authProvider: string;
@property({
type: 'string',
name: 'auth_id',
})
authId?: string;
@property({
type: 'string',
name: 'auth_token',
})
authToken?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
implements Provider<VerifyFunction.FacebookAuthFn> {
constructor(
@repository(UserRepository)
@repository(UserCredentialsRepository)
) {}
value(): VerifyFunction.FacebookAuthFn {
where: {
/* eslint-disable-next-line @typescript-eslint/no-exp
email: (profile as any)._json.email,
},
});
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.Invalid
}
if (
!user ||
) {
authUser.permissions = [];
authUser.externalAuthToken = accessToken;
authUser.externalRefreshToken = refreshToken;
return authUser;
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.FACEBOOK_OAUTH2_VERIFIER).toProvi
FacebookOauth2VerifyProvider,
);
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
response,
);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@authenticate(
STRATEGY.FACEBOOK_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.FACEBOOK_AUTH_URL,
callbackURL: process.env.FACEBOOK_AUTH_CALLBACK_URL,
clientID: process.env.FACEBOOK_AUTH_CLIENT_ID,
clientSecret: process.env.FACEBOOK_AUTH_CLIENT_SECRET,
tokenURL: process.env.FACEBOOK_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/facebook', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginViaFacebook(
@param.query.string('client_id')
clientId?: string,
@param.query.string('client_secret')
clientSecret?: string,
): Promise<void> {}
@authenticate(
STRATEGY.FACEBOOK_OAUTH2,
accessType: 'offline',
authorizationURL: process.env.FACEBOOK_AUTH_URL,
callbackURL: process.env.FACEBOOK_AUTH_CALLBACK_URL,
clientID: process.env.FACEBOOK_AUTH_CLIENT_ID,
clientSecret: process.env.FACEBOOK_AUTH_CLIENT_SECRET,
tokenURL: process.env.FACEBOOK_AUTH_TOKEN_URL,
},
(req: Request) => {
return {
accessType: 'offline',
state: Object.keys(req.query)
.join('&'),
};
},
)
@authorize(['*'])
@get('/auth/facebook-auth-redirect', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async facebookCallback(
): Promise<void> {
where: {
clientId: clientId,
},
});
if (!client || !client.redirectUrl) {
try {
clientId,
user: this.user,
};
expiresIn: client.authCodeExpiration,
audience: clientId,
subject: this.user.username,
issuer: process.env.JWT_ISSUER,
});
response.redirect(`${client.redirectUrl}?code=${token}`);
} catch (error) {
Please note above that we are creating two new APIs for facebook auth. The first one
is for UI clients to hit. We are authenticating client as well, then passing the details to
the facebook auth. Then, the actual authentication is done by facebook authorization
url, which redirects to the second API we created after success. The first API method
body is empty as we do not need to handle its response. The facebook auth provider
in this package will do the redirection for you automatically.
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
Keycloak
First, create a AuthUser model implementing the IAuthUser interface. You can
implement the interface in the user model itself. See sample below.
@model({
name: 'users',
})
@property({
type: 'number',
id: true,
})
id?: number;
@property({
type: 'string',
required: true,
name: 'first_name',
})
firstName: string;
@property({
type: 'string',
name: 'last_name',
})
lastName: string;
@property({
type: 'string',
name: 'middle_name',
})
middleName?: string;
@property({
type: 'string',
required: true,
})
username: string;
@property({
type: 'string',
})
email?: string;
@property({
type: 'string',
required: true,
name: 'auth_provider',
})
authProvider: string;
@property({
type: 'string',
name: 'auth_id',
})
authId?: string;
@property({
type: 'string',
name: 'auth_token',
})
authToken?: string;
@property({
type: 'string',
})
password?: string;
constructor(data?: Partial<User>) {
super(data);
Create CRUD repository for the above model. Use loopback CLI.
lb4 repository
Add the verifier function for the strategy. You need to create a provider for the same.
You can add your application specific business logic for client auth here. Here is a
simple example.
import {
AuthErrorKeys,
IAuthUser,
VerifyFunction,
} from 'loopback4-authentication';
implements Provider<VerifyFunction.KeycloakAuthFn> {
constructor(
@repository(UserRepository)
@repository(UserCredentialsRepository)
) {}
value(): VerifyFunction.KeycloakAuthFn {
email: profile.email,
},
});
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.Invalid
}
where: {
},
});
if (
!creds ||
) {
...user,
});
authUser.permissions = [];
authUser.externalAuthToken = accessToken;
authUser.externalRefreshToken = refreshToken;
return authUser;
};
}
this.component(AuthenticationComponent);
this.bind(Strategies.Passport.KEYCLOAK_VERIFIER).toProvider(
KeycloakVerifyProvider,
);
/**
*/
constructor(
@inject(AuthenticationBindings.USER_AUTH_ACTION)
) {}
try {
response,
);
this.send(response, result);
} catch (err) {
this.reject(context, err);
After this, you can use decorator to apply auth to controller functions wherever
needed. See below.
@authenticateClient(STRATEGY.CLIENT_PASSWORD)
@authenticate(
STRATEGY.KEYCLOAK,
host: process.env.KEYCLOAK_HOST,
@authorize({permissions: ['*']})
@get('/auth/keycloak', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async loginViaKeycloak(
@param.query.string('client_id')
clientId?: string,
@param.query.string('client_secret')
clientSecret?: string,
): Promise<void> {}
@authenticate(
STRATEGY.KEYCLOAK,
host: process.env.KEYCLOAK_HOST,
realm: process.env.KEYCLOAK_REALM,
clientID: process.env.KEYCLOAK_CLIENT_ID,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
callbackURL: process.env.KEYCLOAK_CALLBACK_URL,
authorizationURL: `${process.env.KEYCLOAK_HOST}/auth/real
tokenURL: `${process.env.KEYCLOAK_HOST}/auth/realms/${pro
userInfoURL: `${process.env.KEYCLOAK_HOST}/auth/realms/${
},
keycloakQueryGen,
@authorize({permissions: ['*']})
@get('/auth/keycloak-auth-redirect', {
responses: {
[STATUS_CODE.OK]: {
content: {
[CONTENT_TYPE.JSON]: {
},
},
},
},
})
async keycloakCallback(
): Promise<void> {
where: {
clientId,
},
});
if (!client || !client.redirectUrl) {
try {
user: this.user,
};
expiresIn: client.authCodeExpiration,
audience: clientId,
subject: this.user.username,
issuer: process.env.JWT_ISSUER,
});
response.redirect(
`${client.redirectUrl}?code=${token}&user=${this.user.u
);
} catch (error) {
this.logger.error(error);
Please note above that we are creating two new APIs for keycloak auth. The first one
is for UI clients to hit. We are authenticating client as well, then passing the details to
the keycloak auth. Then, the actual authentication is done by keycloak authorization
url, which redirects to the second API we created after success. The first API method
body is empty as we do not need to handle its response. The keycloak auth provider
in this package will do the redirection for you automatically.
For accessing the authenticated AuthUser model reference, you can inject the
CURRENT_USER provider, provided by the extension, which is populated by the auth
action sequence above.
@inject.getter(AuthenticationBindings.CURRENT_USER)
@authenticate(
STRATEGY.BEARER,
undefined, //options
undefined, //authOptions
VerifyBindings.BEARER_SIGNUP_VERIFY_PROVIDER,
this.bind(VerifyBindings.BEARER_SIGNUP_VERIFY_PROVIDER).toProvi
LocalPreSignupProvider as Constructor<Provider<PreSignupFn>>,
);
Feedback
If you've noticed a bug or have a question or have a feature request, search the
issue tracker to see if someone else in the community has already created a ticket. If
not, go ahead and make one! All feature requests are welcome. Implementation time
may vary. Feel free to contribute the same, if you can. If you think this extension is
useful, please star it. Appreciation really helps in keeping this project alive.
Contributing
Please read CONTRIBUTING.md for details on the process for submitting pull
requests to us.
Code of conduct
Code of conduct guidelines here.
License
MIT