Comments (42)
What about a class/method level @UseMiddleware(fn)
decorator that applies the middleware?
from tsoa.
I just tried to copy paste something simple together, our setup is basically something like this:
let initialized = false;
function registerStrategies() {
if (initialized) return;
passport.use(
'BASIC',
new BasicStrategy(async (username, password, done) => {
try {
const user = await verifyLogin(username, password);
done(null, user);
} catch (e) {
done(e);
}
})
);
passport.use(
'BEARER',
new BearerStrategy(async (token, done) => {
try {
const user = await verifyToken(token);
done(null, user);
} catch (e) {
done(e);
}
});
);
initialized = true;
}
export async function expressAuthentication(
request: express.Request,
securityName: string,
scopes?: string[]
): Promise<any> {
registerStrategies();
const strategy: any = passport.authenticate(securityName, {
session: false
});
const authResult = await new Promise((resolve, reject) =>
strategy(request, request.res, err => {
if (err) {
reject(err);
} else {
resolve(request.user);
}
})
);
return authResult;
}
and then you can use fo example @Security('BEARER') or @Security('BASIC')
from tsoa.
It doesn't work with passportjs since passport requires req, res and next and only req is passed to the method.
from tsoa.
Hello @dgreene1, thanks for the efforts.
In our case, we must use auth0 authentication so we need their auth middleware to secure apis. But some GET endpoints need to be public, so ideally for each endpoint with this new middleware decorator, it will use some specific middleware(s), otherwise bypass it.
// Below is required from auth0
export const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: AUTH0_AUDIENCE,
issuer: `https://${AUTH0_DOMAIN}/`,
algorithm: ['RS256']
});
// Controller
@CustomMiddleware(['auth0', 'others'])
... ...
In regards to the swagger docs, only an auth token header is required if we have @Security decorator. For other use cases, I don't have a clue though.
Any idea?
from tsoa.
I had to wrap an express middleware so that I could use it with tsoa. It would be nice if it supported this interface directly
from tsoa.
Could you please provide an example of defining coustom route template and wiring the middleware?
Thank you.
I would love to see any example of this too. @lukeautry, thanks.
from tsoa.
I’d like to find a way to document how to do this without a custom template. I believe it should be possible to do with the @request decorator. I’ll reopen this issue as a note to myself.
from tsoa.
I think if someone could just show how to use passport.js within the tsoa authentication function ( https://github.com/lukeautry/tsoa/blob/master/README.MD#authentication ) then we would be good to go. Because that authentication function is only called when the Security decorator is on the route. I don’t have any knowledge of passport so I can’t help with creating that readme example.
But now that I’m looking at that example in https://github.com/lukeautry/tsoa/blob/master/README.MD#authentication I’m wondering why you would still need PassportJS when it’s very easy to create a secure route via the readme’s example. It shows how to expect specific scopes etc.
from tsoa.
You can, but you still probably shouldn't.
Easiest way is to call it from the route handler like a regular method.
@WoH Thanks for your suggestion, I am gonna use it. But I think the middleware approach make it easier to maintain a controller. The controller should not care about some details before to receive the request. Middleware makes this abstraction possible.
from tsoa.
Coming back here, since project I want to migrate to leverage tsoa, uses a couple of middleware for stuff like tenancy and auth
We have a authentication module for that.
@WoH I'd appreciate a example using the route handler pattern you described
If you previously did (req, res, next) => void
, where you mutate the request object, just import a function (actuallyneededParams) => whateverYouSetOnRequest
and call it in the handler.
@WoH Thanks for your suggestion, I am gonna use it. But I think the middleware approach make it easier to maintain a controller. The controller should not care about some details before to receive the request. Middleware makes this abstraction possible.
Middleware in express has too many pitfalls (performance, async handling, passing res around, mutating req etc.), I'd rather not get into that discussion here though.
from tsoa.
Are there any other thoughts regarding this? It would be really handy to be able to use keycloak with tsoa nicely.
from tsoa.
@jonzee With #81 you can now define a custom route template; I believe this should solve this issue for you. Let me know what you think.
from tsoa.
Could you please provide an example of defining coustom route template and wiring the middleware?
Thank you.
from tsoa.
@simllll we’d love a PR for this if you’d like to add a middleware decorator. I’d consider looking into how the routing-controllers library does it. If we can keep parity with them it would help people migrate over to tsoa.
from tsoa.
I'd like some more information before we introduce a milldeware decorate. So, @janhorubala @anzemur @luli0822 @Paldom @ezra-quemuel @myflowpl @ezra-quemuel @HarelM @simllll...
I'd like to know:
- Which is more important:
[ ] A middleware decorator that is called before the route?
[ ] or one that is called after the route? - What kind of transformations of the request type are you expecting to be able to accomplish?
- What kind of side effects are you expecting to be able to add? (for instance, sometimes you might write to a totally unrelated data store inside of a middleware)
- And most importantly can you please describe what impacts the middleware should have in documenting swagger? (i.e. if you are rejecting anything that doesn't have a specific header, shouldn't that be documented in the swagger and not just something you do ad-hoc in a pre-middleware?)
from tsoa.
@dgreene1 Our use case is one that can inject a middleware much like how you would add it to the express pipe (a function that receives req, res, next
). We are using passport before the route. It would help to tell swagger that a specific route needs authentication. We are using JWT so swagger only needs to add the relevant header when authenticated in swagger UI.
Our current solution is a custom template, but we would prefer decorator if possible...
Thanks for taking the time to review our needs!
from tsoa.
@HarelM Any reasons you're not using @Security
?
https://github.com/lukeautry/tsoa#authentication
from tsoa.
I should have been more detailed with my description.
- Add the passport middleware before the call to RegisterRoutes (I assumed that's what you did)
- Use
@Security("jwt")
to check if the context is allowed to perform the action - Add something like this to tsoa config:
"securityDefinitions": {
"jwt": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
from tsoa.
@WoH I assure you I tried it all before posting my comment here :-)
The current template doesn't work with passport due to how passport middleware is.
from tsoa.
It doesn't work with passportjs since passport requires req, res and next and only req is passed to the method.
Wrong if you app.use it before routes. Maybe there are other reasons?
The current template doesn't work with passport due to how passport middleware is.
Well I guess that's very specific.
What you are saying is it does not work, but you can't tell me why.
However, an unspecified middleware decorator, where it is not clear how/when it would be applied, will?
I'll leave it up to Dan to engage, but I won't be able to help here.
from tsoa.
I might be missing out something then, if you can send a short working example when not all routes are secured I'll be delighted. I think this can't be the case with app.use(...)
When using the secured attribute the documentation states that you need to provide a method - this method receives only the request object - it is not enough for passport.
Again, I might be wrong. If you can post here a working example it will be super.
from tsoa.
A decorator that operates before the call, that works similarly to how express works, will allow adding things that are documnted for express but not for tsoa. Yes, it will be very helpful.
The other solution is to pass both req, res, and next to the security method. Which is more specific to my problem.
from tsoa.
I can send the custom template we are using to make passport work.
Passport has integration with mongo that comes out of the box to solve password hashing etc. This is one of the reasons. Another is that I prefer to use code that was battle tested instead of writing my own... :-)
from tsoa.
The following is our template:
Note where the authenticateMiddleware
is placed.
This is the only way I found that made passport work.
This is also how other middlewares in express are usually done whenever you find a middleware example.
Allowing TSOA to decorate a route with a middleware will allow TSOA users like me easier integration with third party middlewares that has an example that is written in pure js/express only.
/* tslint:disable */
{{#if canImportByAlias}}
import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute } from 'tsoa';
{{else}}
import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute } from '../../../src';
{{/if}}
{{#if iocModule}}
import { iocContainer } from '{{iocModule}}';
{{/if}}
{{#each controllers}}
import { {{name}} } from '{{modulePath}}';
{{/each}}
import * as passport from 'passport';
import * as express from 'express';
const models: TsoaRoute.Models = {
{{#each models}}
"{{@key}}": {
{{#if enums}}
"enums": {{{json enums}}},
{{/if}}
{{#if properties}}
"properties": {
{{#each properties}}
"{{@key}}": {{{json this}}},
{{/each}}
},
{{/if}}
{{#if additionalProperties}}
"additionalProperties": {{{json additionalProperties}}},
{{/if}}
},
{{/each}}
};
const validationService = new ValidationService(models);
export function RegisterRoutes(app: express.Express) {
{{#each controllers}}
{{#each actions}}
app.{{method}}('{{fullPath}}',
{{#if security.length}}
authenticateMiddleware({{json security}}),
{{/if}}
function (request: any, response: any, next: any) {
const args = {
{{#each parameters}}
{{@key}}: {{{json this}}},
{{/each}}
};
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request);
} catch (err) {
return next(err);
}
{{#if ../../iocModule}}
const controller = iocContainer.get<{{../name}}>({{../name}});
if (typeof controller['setStatus'] === 'function') {
(<any>controller).setStatus(undefined);
}
{{else}}
const controller = new {{../name}}();
{{/if}}
const promise = controller.{{name}}.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
{{/each}}
{{/each}}
{{#if useSecurity}}
function authenticateMiddleware(security: TsoaRoute.Security[] = []) {
for (const secMethod of security) {
for (const name in secMethod) {
return passport.authenticate(name, { session: false, failWithError: true }); // yes we return the first auth...
}
}
}
{{/if}}
function isController(object: any): object is Controller {
return 'getHeaders' in object && 'getStatus' in object && 'setStatus' in object;
}
function promiseHandler(controllerObj: any, promise: any, response: any, next: any) {
return Promise.resolve(promise)
.then((data: any) => {
let statusCode;
if (isController(controllerObj)) {
const headers = controllerObj.getHeaders();
Object.keys(headers).forEach((name: string) => {
response.set(name, headers[name]);
});
statusCode = controllerObj.getStatus();
}
if (data || data === false) { // === false allows boolean result
response.status(statusCode || 200).json(data);
} else {
response.status(statusCode || 204).end();
}
})
.catch((error: any) => next(error));
}
function getValidatedArgs(args: any, request: any): any[] {
const fieldErrors: FieldErrors = {};
const values = Object.keys(args).map((key) => {
const name = args[key].name;
switch (args[key].in) {
case 'request':
return request;
case 'query':
return validationService.ValidateParam(args[key], request.query[name], name, fieldErrors);
case 'path':
return validationService.ValidateParam(args[key], request.params[name], name, fieldErrors);
case 'header':
return validationService.ValidateParam(args[key], request.header(name), name, fieldErrors);
case 'body':
return validationService.ValidateParam(args[key], request.body, name, fieldErrors, name + '.');
case 'body-prop':
return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, 'body.');
}
});
if (Object.keys(fieldErrors).length > 0) {
throw new ValidateError(fieldErrors, '');
}
return values;
}
}
from tsoa.
Maybe I can find time to document our setup (passport/mongo) without custom template, but most of the ideas I already shared in #62 (comment)
Tl;dr is: Use global mw to set up the request obj and use @Security
to verify on secure routes.
from tsoa.
@WoH can you share your security method?
from tsoa.
I have no issues reagding @Security or passport middlware, but several other use cases:
Therefore here my answer to your questions @dgreene1
Which is more important:
[X] A middleware decorator that is called before the route?
[ ] or one that is called after the route?
What kind of transformations of the request type are you expecting to be able to accomplish?
- add cors headers (not for all routes, only for specific ones)
- add locals for translations, date / time stuff, depending on the route (not global)
- overwrite default response handlers (e.g. we overwrite res.json for some routes because the resulting json has several hundret megabytes, and default json.stringify is blocking, but overwriting res.json with e.g. bfj implemntaiton of stringify does this in a non-blocking fashion.
- apply a rate limiter to some routes
- add additional req properties (e.g. request ip, ...)
- additional logging middleware (e.g. log request body and do some stuff when request is completed
What kind of side effects are you expecting to be able to add? (for instance, sometimes you might write to a totally unrelated data store inside of a middleware)
- inside the midleware I need to have all kind of freedoms, use external libs, do datebase stuff or whatever.. .g. ratelimiter uses redis behind the scene, rqeuest ip lib uses some other node npm lib, ..etc etc.
And most importantly can you please describe what impacts the middleware should have in documenting swagger? (i.e. if you are rejecting anything that doesn't have a specific header, shouldn't that be documented in the swagger and not just something you do ad-hoc in a pre-middleware?)
- all of our use cases should not have any impact on the documentation
- but it would be great if we can covr the file upload use cases in a general fashin too. which means add e.g. the multipart/form-data to the output. Or for example we have use cases where we allow application/json OR multipart/form-data with additoinal propertites. e.g. i send firstname and lastname as stirng and an image (binary).. this can be done via multipart/form-data. but json/applicatin calls can still be done for all "non upload" calls.
- I would say "by definition" a middleware shouldn't reject anything, this should (if at all) only be possible inside the custom validators (#496) (but hard to distinguish in some cases I guess, ..e.g. multer is a middleware and can throw input validation errors)
from tsoa.
@simllll can you share your security method and/or configuration to allow passport and tsoa without a custom template?
from tsoa.
Is there any progress on this. I am working on an express sever that should be able to call keycloack-connect middlewares. I tried to solves this via the security decorator, but i dont know how to go on here. It would be easier if we could define RequestHandlers that is automatically called.
What I found out is that keycloak-connect needs access to the req, res and next variables
from tsoa.
is there any progress on this?
Short version: please open a PR if you want this.
@kdankert none of the maintainers uses keycloak or passport. I don’t use middleware at all since I find they make the logic more confusing. That’s not to say I don’t think tsoa should have them though. I think they should. But since myself and the other maintainers don’t have the personal knowledge to investigate this there will not be any progress until someone from the community opens up a PR.
from tsoa.
@dgreene1 a PR for a middleware will be a accepted? How is the situation about custom validators? (If we do a distinction between these two at all). I will see if I can find time for a PR that allows a simple @Middleware annotation,I guess this can be done easily and without increasing the complexity of tsoa, but solves some use cases.
from tsoa.
@dgreene1 I think we should be more specific here and lay out acceptance criteria for that PR since middleware may not only impact security and validation, but also documentation. Firthermore, @Middleware
should be compatible with all the frameworks we support I assume, that should also be a requirement I think.
from tsoa.
Correct. A PR for general middleware needs more discussion.
However, I was encouraging @kdankert to make a PR so that the security decorator can send all of the parts that’s express middleware typically need. Maybe that should be a separate github issue so we can track that by itself while we flesh out the acceptance criteria for general purpose middleware and/ validators.
from tsoa.
Middlewares have slight problem though, they are not supported by every framework
from tsoa.
I am using passportjs, and crowd strategy for session based authentication and I'm confused how to write isAuthenticate middleware in my services any guidance how to do it
from tsoa.
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days
from tsoa.
I would like to have bearerFormat
forwarded into swagger 🤔
"type": "http",
"name": "Authorization",
"in": "header",
"scheme": "bearer",
"bearerFormat": "JWT"
from tsoa.
Hi
Is there any update on using middlewares at method level?
Thanks
from tsoa.
Hi
Is there any update on using middlewares at method level?
Thanks
You can, but you still probably shouldn't.
Easiest way is to call it from the route handler like a regular method.
from tsoa.
Coming back here, since project I want to migrate to leverage tsoa, uses a couple of middleware for stuff like tenancy and auth
from tsoa.
@WoH I'd appreciate a example using the route handler
pattern you described
from tsoa.
Also a passport user, I'd be happy to eject passport for built in auth but there's too much business logic in there (open id connect logic, oauth2, etc.).
It would be amazing if the expressAuthentication
function behaved like a middleware (the RequestHandler
type in express, accepting a req: Request, res: Response, Next:
signature).
From the generated code it looks like the security is already a middleware that manipulates the response object, see these lines:
tsoa/tests/fixtures/custom/custom-tsoa-template.ts.hbs
Lines 72 to 98 in 5259731
I'm basically looking for a way to use the Security()
decorator to declare which authentication logic to apply and keep that in sync with my docs. The actual authentication logic (and how it manipulates my response etc.) I'd prefer to keep in passport.
My current workaround is to use @Middleware
, which applies auth correctly but I doesn't update my openapi docs.
@WoH would you be open to a PR to accept middlewares as the security handler?
from tsoa.
Related Issues (20)
- PR 1498 break generating spec or routes HOT 4
- Duplicate component name in spec HOT 1
- Help requested: Using in-memory caching with TSOA HOT 2
- GenerateMetadataError: Unknown type: BigIntKeyword HOT 2
- Support string variable in the first argument of @Extension HOT 1
- Allow providing function as well as string in @Route and @Get @Post etc HOT 6
- Including `length` as an enum value breaks spec generation HOT 2
- support for multiple server HOT 1
- Support for inheritance in controllers HOT 2
- When will TSOA 6.0 be released? HOT 2
- Compiler Problems HOT 2
- Unused imports and parameters in generated hapi routes file HOT 2
- Can't call methods from @Inject class using typescript-io. HOT 1
- "$" char percent encoding causes reference and pointer resolving issue HOT 2
- request body @pattern validation has no effect after upgrade to v6.0.0 HOT 5
- Add ability for custom annotations to write Extensions HOT 1
- Validation erros not show in version 6.0.0 HOT 2
- 6.0.0 regression: GenerateMetadataError: Unknown type: NeverKeyword HOT 7
- [Question] Invoking CLI function programattically HOT 6
- Using `Omit` on a type, tsoa ignores regex pattern validation on field in that type HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tsoa.