Coder Social home page Coder Social logo

Comments (42)

WoH avatar WoH commented on April 27, 2024 5

What about a class/method level @UseMiddleware(fn) decorator that applies the middleware?

from tsoa.

simllll avatar simllll commented on April 27, 2024 4

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.

HarelM avatar HarelM commented on April 27, 2024 3

It doesn't work with passportjs since passport requires req, res and next and only req is passed to the method.

from tsoa.

luli0822 avatar luli0822 commented on April 27, 2024 2

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.

dr3s avatar dr3s commented on April 27, 2024 2

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.

luli0822 avatar luli0822 commented on April 27, 2024 1

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.

dgreene1 avatar dgreene1 commented on April 27, 2024 1

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.

dgreene1 avatar dgreene1 commented on April 27, 2024 1

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.

tgouala avatar tgouala commented on April 27, 2024 1

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.

WoH avatar WoH commented on April 27, 2024 1

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.

grug avatar grug commented on April 27, 2024 1

Are there any other thoughts regarding this? It would be really handy to be able to use keycloak with tsoa nicely.

from tsoa.

lukeautry avatar lukeautry commented on April 27, 2024

@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.

anzemur avatar anzemur commented on April 27, 2024

Could you please provide an example of defining coustom route template and wiring the middleware?

Thank you.

from tsoa.

dgreene1 avatar dgreene1 commented on April 27, 2024

@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.

dgreene1 avatar dgreene1 commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

@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.

WoH avatar WoH commented on April 27, 2024

@HarelM Any reasons you're not using @Security?
https://github.com/lukeautry/tsoa#authentication

from tsoa.

WoH avatar WoH commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

@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.

WoH avatar WoH commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

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.

WoH avatar WoH commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

@WoH can you share your security method?

from tsoa.

simllll avatar simllll commented on April 27, 2024

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.

HarelM avatar HarelM commented on April 27, 2024

@simllll can you share your security method and/or configuration to allow passport and tsoa without a custom template?

from tsoa.

kdankert avatar kdankert commented on April 27, 2024

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.

dgreene1 avatar dgreene1 commented on April 27, 2024

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.

simllll avatar simllll commented on April 27, 2024

@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.

WoH avatar WoH commented on April 27, 2024

@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.

dgreene1 avatar dgreene1 commented on April 27, 2024

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.

kdankert avatar kdankert commented on April 27, 2024

Middlewares have slight problem though, they are not supported by every framework

from tsoa.

parikxxit avatar parikxxit commented on April 27, 2024

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.

github-actions avatar github-actions commented on April 27, 2024

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.

williamdes avatar williamdes commented on April 27, 2024

I would like to have bearerFormat forwarded into swagger 🤔

                "type": "http",
                "name": "Authorization",
                "in": "header",
                "scheme": "bearer",
                "bearerFormat": "JWT"

from tsoa.

jbleyaert avatar jbleyaert commented on April 27, 2024

Hi

Is there any update on using middlewares at method level?

Thanks

from tsoa.

WoH avatar WoH commented on April 27, 2024

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.

koolamusic avatar koolamusic commented on April 27, 2024

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.

koolamusic avatar koolamusic commented on April 27, 2024

@WoH I'd appreciate a example using the route handler pattern you described

from tsoa.

owlas avatar owlas commented on April 27, 2024

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:

{{#if useSecurity}}
function authenticateMiddleware(securities: TsoaRoute.Security[] = []) {
return (request: any, _response: any, next: any) => {
let responded = 0;
let success = false;
Object.keys(securities)
.forEach(name => {
expressAuthentication(request, name, securities[name]).then((user: any) => {
// only need to respond once
if (!success) {
success=true;
responded++;
request['user']=user;
next();
}
})
.catch((error: any) => {
responded++;
if (responded==securities.length&&!success) {
_response.status(error.status || 401);
next(error)
}
})
})
}
}
{{/if}}

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.