-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: enable swagger-ui token #267
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,12 +84,6 @@ This app has five services: | |
5. `services/jwt-service` - responsible for generating and verifying JSON Web | ||
Token. | ||
|
||
## Authentication | ||
|
||
_Note: This app contains a `login` endpoint for the purpose of spike and demo, | ||
the authentication for the CRUD operations and navigational endpoints of model | ||
User is still in progress._ | ||
|
||
### Login | ||
|
||
The endpoint for logging in a user is a `POST` request to `/users/login`. | ||
|
@@ -110,13 +104,126 @@ possible by the use of the `UserService` service provided by | |
You can see the details in | ||
[`packages/shopping/src/controllers/user.controller.ts`](https://github.com/strongloop/loopback4-example-shopping/blob/master/packages/shopping/src/controllers/user.controller.ts). | ||
|
||
### Tutorial | ||
## Authentication | ||
|
||
### Enabling Auth Token with Swagger-ui | ||
|
||
`swagger-ui` module is built with authorization component, which will show up by | ||
adding the security schema and operation security spec in the OpenAPI spec. | ||
|
||
You can check the OpenAPI | ||
[doc](https://swagger.io/docs/specification/authentication/bearer-authentication/) | ||
to learn how to add it, see section "Describing Bearer Authentication". | ||
|
||
As a demo, the security related specs are hardcoded and merged into the | ||
application's OpenAPI spec in the main file in this spike. | ||
|
||
### Setting token | ||
|
||
_Should be moved to | ||
https://loopback.io/doc/en/lb4/Authentication-Tutorial.html#try-it-out_ | ||
|
||
After creating a user, you can login with `email`and `password`: | ||
|
||
![login](/imgs/login.png) | ||
|
||
A JWT token will be generated and returned in the response body, you can copy | ||
the token for setting the bearer header in the next step: | ||
|
||
![get-token.png](/imgs/get-token.png) | ||
|
||
Then set the token for every request's Bearer header. You will find a green | ||
button called "Authorize" on the right corner of the explorer: | ||
|
||
![authorize-button](/imgs/authorize-button.png) | ||
|
||
Click it and the token set dialog will be prompted: | ||
|
||
![set-token](/imgs/set-token.png) | ||
|
||
Paste the token you just copied in the field, then click "Authorize". The token | ||
will be hidden: | ||
|
||
![after-set-token](/imgs/after-set-token.png) | ||
|
||
Now you can try endpoint like `GET/users/me` to verify that the logged in user | ||
is injected in the request: | ||
|
||
![me](/imgs/me.png) | ||
|
||
### Operation Level Security Policy | ||
|
||
You can also specify security policy for a single endpoint, by adding the | ||
security spec in the operation decorator, like: | ||
|
||
```ts | ||
// Define your operation level security policy | ||
const SECURITY_SPEC_OPERATION = { | ||
[{basicAuth: []}]; | ||
} | ||
|
||
// Also add the corresponding security schema into `components.securitySchemas` | ||
const SECURITY_SCHEMA_SPEC = { | ||
securitySchemes: { | ||
bearerAuth: { | ||
type: 'http', | ||
scheme: 'bearer', | ||
bearerFormat: 'JWT', | ||
}, | ||
basicAuth: { | ||
type: 'http', | ||
scheme: 'basic', | ||
}, | ||
}, | ||
}; | ||
|
||
// Add security policy in the operation decorator | ||
@get('/users/{userId}', { | ||
security: SECURITY_SPEC_OPERATION, | ||
responses: RESPONSE_SPEC, | ||
}) | ||
async findById(@param.path.string('userId') userId: string): Promise<User> { | ||
// Print out the header, you will see the Basic auth string in the header | ||
console.log(`header ${inspect(this.request.headers)}`); | ||
// business logic | ||
} | ||
``` | ||
|
||
The "Authorize" dialog will show up your new added entry as: | ||
|
||
![operation-level-security](/imgs/operation-level-security.png) | ||
|
||
Provide the username and password to login: | ||
|
||
![set-basic-auth](/imgs/set-basic-auth.png) | ||
|
||
Try endpoint `GET users/{userId}`, the printed header contains the basic auth | ||
string: | ||
|
||
![basic-auth-header](/imgs/basic-auth-header.png) | ||
|
||
### Follow-up Stories | ||
|
||
As you could find in the `security-spec.ts` file, security related spec is | ||
hardcoded now and is manually merged into the openapi spec in the main file | ||
`index.ts`, to enable the token set more automatically, there are 3 things we | ||
could improve: | ||
|
||
- The security schema could be contributed by the authentication strategy, see | ||
[story#3669](https://github.com/strongloop/loopback-next/issues/3669) | ||
- This spike sets a global security policy for all the endpoints, while the | ||
policy spec can be set on the operation level, it's also documented in | ||
[swagger/authentication/bearer-authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). | ||
This can be achieved by using `@api()`(controller class level) or | ||
`@authenticate()`(controller method level) | ||
|
||
## Tutorial | ||
|
||
There is a tutorial which shows how to apply the JWT strategy to secure your | ||
endpoint with `@loopback/[email protected]`. You can check more details in | ||
https://loopback.io/doc/en/lb4/Authentication-Tutorial.html | ||
|
||
### Trying It Out | ||
## Trying It Out | ||
|
||
Please check the | ||
[try it out](https://loopback.io/doc/en/lb4/Authentication-Tutorial.html#try-it-out) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,11 +5,19 @@ | |
|
||
import {repository} from '@loopback/repository'; | ||
import {validateCredentials} from '../services/validator'; | ||
import {post, param, get, requestBody, HttpErrors} from '@loopback/rest'; | ||
import { | ||
post, | ||
param, | ||
get, | ||
requestBody, | ||
HttpErrors, | ||
RestBindings, | ||
Request, | ||
} from '@loopback/rest'; | ||
import {User, Product} from '../models'; | ||
import {UserRepository} from '../repositories'; | ||
import {RecommenderService} from '../services/recommender.service'; | ||
import {inject} from '@loopback/core'; | ||
import {inject, Context} from '@loopback/core'; | ||
import { | ||
authenticate, | ||
UserProfile, | ||
|
@@ -23,13 +31,15 @@ import { | |
} from './specs/user-controller.specs'; | ||
import {Credentials} from '../repositories/user.repository'; | ||
import {PasswordHasher} from '../services/hash.password.bcryptjs'; | ||
import {inspect} from 'util'; | ||
|
||
import { | ||
TokenServiceBindings, | ||
PasswordHasherBindings, | ||
UserServiceBindings, | ||
} from '../keys'; | ||
import * as _ from 'lodash'; | ||
import {SECURITY_SPEC_OPERATION} from '../utils/security-spec'; | ||
|
||
export class UserController { | ||
constructor( | ||
|
@@ -42,6 +52,8 @@ export class UserController { | |
public jwtService: TokenService, | ||
@inject(UserServiceBindings.USER_SERVICE) | ||
public userService: UserService<User, Credentials>, | ||
@inject(RestBindings.Http.CONTEXT) public ctx: Context, | ||
@inject(RestBindings.Http.REQUEST) private request: Request, | ||
) {} | ||
|
||
@post('/users', { | ||
|
@@ -83,6 +95,7 @@ export class UserController { | |
} | ||
|
||
@get('/users/{userId}', { | ||
security: SECURITY_SPEC_OPERATION, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, this is configuring There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bajtos Oh this is just a demo, for spike only, I just want to verify that a operation level security policy(which is different than the global
Exactly 💯 |
||
responses: { | ||
'200': { | ||
description: 'User', | ||
|
@@ -97,6 +110,7 @@ export class UserController { | |
}, | ||
}) | ||
async findById(@param.path.string('userId') userId: string): Promise<User> { | ||
console.log(`header ${inspect(this.request.headers)}`); | ||
return this.userRepository.findById(userId, { | ||
fields: {password: false}, | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { SecuritySchemeObject, ReferenceObject } from "@loopback/openapi-v3"; | ||
|
||
export const SECURITY_SPEC = [{bearerAuth: []}]; | ||
export const SECURITY_SPEC_OPERATION = [{basicAuth: []}]; | ||
export type SecuritySchemeObjects = { | ||
[securityScheme: string]: SecuritySchemeObject | ReferenceObject; | ||
} | ||
export const SECURITY_SCHEME_SPEC: SecuritySchemeObjects = { | ||
bearerAuth: { | ||
type: 'http', | ||
scheme: 'bearer', | ||
bearerFormat: 'JWT', | ||
}, | ||
basicAuth: { | ||
type: 'http', | ||
scheme: 'basic', | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
servers: [{url: '/'}]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or config it when create the rest app:
https://loopback.io/doc/en/lb4/Server.html#customize-how-openapi-spec-is-served