I wonder if there is some simple way to use external libraries (such as passport.js) with tsoa. I saw injection of req, but often we need res and next as well. The ideal situation for me is to pass req res next to some middleware (in i.e. /services) that would implement logic of the endpoint.
@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.
Could you please provide an example of defining coustom route template and wiring the middleware?
Thank you.
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.
I鈥檇 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鈥檒l reopen this issue as a note to myself.
What about a class/method level @UseMiddleware(fn) decorator that applies the middleware?
@simllll we鈥檇 love a PR for this if you鈥檇 like to add a middleware decorator. I鈥檇 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.
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:
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?
@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!
@HarelM Any reasons you're not using @Security?
https://github.com/lukeautry/tsoa#authentication
It doesn't work with passportjs since passport requires req, res and next and only req is passed to the method.
I should have been more detailed with my description.
@Security("jwt") to check if the context is allowed to perform the action"securityDefinitions": {
"jwt": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
@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.
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.
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.
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.
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鈥檛 have any knowledge of passport so I can鈥檛 help with creating that readme example.
But now that I鈥檓 looking at that example in https://github.com/lukeautry/tsoa/blob/master/README.MD#authentication I鈥檓 wondering why you would still need PassportJS when it鈥檚 very easy to create a secure route via the readme鈥檚 example. It shows how to expect specific scopes etc.
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... :-)
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;
}
}
Maybe I can find time to document our setup (passport/mongo) without custom template, but most of the ideas I already shared in https://github.com/lukeautry/tsoa/issues/62#issuecomment-546643699
Tl;dr is: Use global mw to set up the request obj and use @Security to verify on secure routes.
@WoH can you share your security method?
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?
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?)
@simllll can you share your security method and/or configuration to allow passport and tsoa without a custom template?
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')
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
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鈥檛 use middleware at all since I find they make the logic more confusing. That鈥檚 not to say I don鈥檛 think tsoa should have them though. I think they should. But since myself and the other maintainers don鈥檛 have the personal knowledge to investigate this there will not be any progress until someone from the community opens up a PR.
@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.
@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.
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鈥檚 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.
Middlewares have slight problem though, they are not supported by every framework
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
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
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
~I would like to have bearerFormat forwarded into swagger :thinking:~
"type": "http",
"name": "Authorization",
"in": "header",
"scheme": "bearer",
"bearerFormat": "JWT"
Hi
Is there any update on using middlewares at method level?
Thanks
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.
Coming back here, since project I want to migrate to leverage tsoa, uses a couple of middleware for stuff like tenancy and auth
@WoH I'd appreciate a example using the route handler pattern you described
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.
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.
Most helpful comment
What about a class/method level
@UseMiddleware(fn)decorator that applies the middleware?