I'm trying to create SSO in my Nest.js application using Okta as Identity Provider and passport-saml library. I read documentation of Nest authentication and passport-saml. I have no problems with understanding of examples, but I really need to use SAML strategy with different configuration which depend on request body value of POST api/auth/saml. In other words, I have one strategy, but with different entryPoint, issuer, cert params for my custom LoginSSOStrategy class which extends PassportStrategy class of Nest.js. Any ideas how can I handle this?
I'm not quite sure if this is a good approach, but if you want, you can make the class request scoped and inject the request via the constructor, then have access to the request object and be able to work with a new instance of your passport strategy each request. You can use the request to pass req.whatever to the super() class's constructor.
#Injectable({ scope: Scope.REQUEST })
export class LoginSSOStrategy exends PassportStrategy(Strategy) {
constructor(#Inject(REQUEST) request: Request, ...) {
super({/* options matching to request.field */});
}
validate(/* validate params*/) {
/* validate functionality */
}
}
This seems like something you'll want to do a lot of testing around and making sure it works with concurrent requests, but overall it could work, in theory at least.
I think this issue is similar to this GitHub issue. Due to the global nature of Passport.js and the fact that Nest is unable to determine which routes use a Passport passport strategy it is not possible to create a request scoped Passport strategy by using #Injectable({ scope: Scope.REQUEST }).
Recently, I had to implement a Azure Active Directory login with a dynamic redirect URL based on some data from the incoming request. Depending on the Strategy you use, you might be able to overwrite some options when calling the authenticate method of a Passport Strategy by using the (undocumented) extraAuthReqQueryParams property.
One way to know wether or not you're able to overwrite some options is to check the documentation, if you're feeling lucky you could look in the source code of the Passport strategy you're using. After reading about the undocumented feature and seeing these lines in the source code of the Azure AD Passport strategy (in particular lines #1355 and #1374) I was able to change the value I previously specified in the redirectUrl property by using the redirect_uri property (note the slight difference here).
#Injectable()
export class AzureOIDCStrategy extends PassportStrategy(OIDCStrategy,'AzureOIDC') {
constructor() {
super({
// Even though it is overwritten in the 'authenticate' method the Passport Strategy expects this to be set to a valid URL.
redirectUrl: `https://your-backend-domain.com/auth/azure/callback`,
// This ensures we have access to the request in the `authenticate` method
passReqToCallback: true,
});
}
authenticate(req: Request, options: Record<string, any>): void {
return super.authenticate(req, {
// `options` may contain more options for the `authenticate` method in Passport.
...options,
extraAuthReqQueryParams: {
// This overwrites the `redirectUrl` specified in the constructor
redirect_uri: `https://${req.headers.host}/auth/callback`,
},
});
}
}
I hope you will be able to apply this 'strategy' to update the entryPoint, issuer and cert params.
In an Express app you could do something like this:
app.get('/login',
(req, res, next) =>
passport.authenticate('azure-ad', {
extraAuthReqQueryParams: {
redirect_uri: `https://${req.headers.host}/auth/callback`,
},
})(req, res, next)
);
Related
I'm newbie with SailJS.
I want to create a new route, so I added this line in config/routes.js:
'GET /operation/:operationId/sums': 'OperationController.getSums',
Then my function:
getSums: async function (req, res) {
return res.status(200).json("OK");
}
But I just get:
Forbidden
with a 403 status code. Other routes works well.
What can I do to fix it ?
Probably some middleware is rejecting this request. Try looking in your config/policies.js file.
It's possible there to set default permissions for all routes, or all routes for a given controller to false, ie, permit nobody (and in that case then you'd usually go in and override the defaults for particular routes).
Look for '*': false or OperationController: { '*': false }. If you don't see those, then try to see what routes are attached to your new route. Possibly, a custom created policy is rejecting the request.
I have been trying to learn NodeJS for quite some time now. All the books and tutorials seems to follow similar pattern of structuring their code. Example -
const express = require('express');
const app = express();
app.set('view engine','hbs');
app.get('/', (req, res) =>{
res.render('index');
});
app.get('/getName', (req, res) =>{
// Mock DB call to fetch Name
res.render('displayName');
});
app.listen(3000, () => {
console.log("Started server on port : 3000");
});
As you can see above, the /getName controller is performing DB call as well as returning the view. So the business logic as well as the CRUD operation is being done at the same place.
I come from the world of JAVA and there we do it slightly differently. For example, a Spring Boot application would contain the following structure -
DTO
Repository
Service
Controller
So, controller classes are the actual endpoints which do not perform any business logic but call the underlying service class to handle all that. The service classes implement the business logic and persist/fetch data required for it with the help of the repository classes. The repository on the other hand handles the CRUD operations.
This seemed like a sane way to develop software. Given that each class has their defined roles, it becomes really easy to handle any change.
I understand the NodeJs is a dynamic but -
1. Is there a way to separate out the functionality like we do in Spring ? If not,
2. How to structure large projects having multiple endpoints and DB transactions.
Regards
EDIT -
Consider the following scenario -
I have a requirement, where I need to fetch a list of users from the database whose status is True ( assume status is a boolean field in the model ).
In Java -
#Service
public class UserService {
#Autowired
UserDetailRepository userDetailRepository;
#Override
public UserDetail getUserDetail (boolean status) {
UserDetail userDetail = UserDetailRepository .findUserDetailByStatus(status);
return userDetail ;
}
Controller.java -
#GetMapping("/getUserDetails")
public ArrayList<UserDetail> getUserDetail(){
return UserService.getUserDetail(true);
}
Now, if the requirement changes and there needs to be a new endpoint that returns only top 10 user details whose status is true. In that case, we can add a new controller and just limit the returned results to 10. We can make use of the same business logic/ service class.
Controller.java
#GetMapping("/getUserDetailsTop10")
public ArrayList<UserDetail> getUserDetail(){
List<UserDetails> users = UserService.getUserDetail(true);
// Filter the list to send top 10
// return users .
}
If I have to implement the same use case in NodeJS, I'll have to write the business logic to fetch the user twice -
const express = require('express');
const app = express();
app.set('view engine','hbs');
app.get('/getUserDetails', (req, res) =>{
// Call DB and get users whose status is True
res.send(userdetails);
});
app.get('/getUserDetailsTop10', (req, res) =>{
// Call DB and get users whose status is True
// Filter the returned results and limit the result to 10 values only
res.send(userdetails);
});
app.listen(3000, () => {
console.log("Started server on port : 3000");
});
At best, I can abstract away this logic into a function, that will return a list of users with status True but then again this approach is not very scalable. There has to be a complete separation of Business logic from controller.
Not really an answer... but some thoughts, as I come from C# and started as a NodeJs developer 3 years ago.
Dependency Injection
This is (sadly) rarely used in many NodeJs projects that I see. There are some npm modules that provide this feature, but none of them have convinced me with a proper approach and API. Dependency Injection is still perfectly possible, only, a bit uglier because of some boilerplate code you have to write. So sadly no easy #autowire approaches. So yes you can do Dependency Injection like with Spring, but not as convenient. But let my opinion not stop you from researching Dependency Injection libraries for node.
Architecture
You can still use the concepts of DTOs, Repositories, Services and Controllers. Only for some (odd) reason, the majority of tutorials and guides out there forget about common sense architecture and just throw everything within a single controller. Don't let them seduce you in forgetting about the concepts you learned in Java. Not that OOP and Java doesn't have flaws, but there is a difference in "writing JavaScript style code" and "let's forget about proper architecture all together".
Do note that you might see some more "functional programming" patterns rise in the NodeJs community, which is not bad (but neither is OOP).
How to structure large projects having multiple endpoints and DB
transactions.
Ignore the bad examples you see out there, just follow your gut feeling from your Java experience how to structure your code, with the right layers, responsibilities and Design Patterns. Only keep in mind that you don't have interfaces and there is no easy alternative to that. So you'll have to learn to work your way around that, but you can still write elegant code without them.
Middleware
Middleware is a common concept for NodeJs applications, they are similary-ish to the proxy/aspect oriented programming style. You might find example projects with excessive amount of middleware, don't let yourself be tempted by that either. Middleware is good for auth/serialisation/headers/security but not for business logic. I've seen middleware hell and middleware driven development, it ain't pretty.
Repository
A lot of people use directly the MongoDb/Mongoose/SQL client without using the adapter pattern like Repository, leaking MongoDb queries all over their solution.
My advice for you would be, just use the same structure and approach you are familiar with, but with the tools that JavaScript gives you. I genuinely love JavaScript, but it makes me cringe when I look at the lack of design and architecture that I see in the mainstream resources, sure small projects and PoC's don't need extensive design, but often when project grow, they don't clean up. How you structure a project should be agnostic to the language and framework you are writing in.
Happy coding 😄
An idea:
const express = require('express');
const app = express();
const { userDetails } = require('./usersBusinessLogic.js')
app.set('view engine','hbs');
app.get('/getUserDetails', async (req, res) =>{
const limit = parseInt(req.query.limit || '0')
if (IsNaN(limit)) {
res.status(400)
return res.end('limit expected to be a number')
}
const detailsResponse = await userDetails({ limit })
res.send();
});
And then you write your logic as function userDetails({ limit, ...rest } = { limit: 0 }).
In another file i.e. usersBusinessLogic.js:
async function userDetails({ limit, ...restOfTheOptions } = { limit: 0 }) {
console.log({ limit, restOfTheOptions }) // TODO logic here.
// const result = await db.find('users', {...})
// return serialize(result)
}
// function serialize(result) {
// return JSON.stringify(result)
// }
module.exports = {
userDetails
}
After which you can call GET /userDetails or GET /userDetails?limit=10.
Let me know what do you think.
UserService.js
import { userDetailRepository } from '../models/user';
class UserService {
getUserDetail (status) {
const userDetail = UserDetailRepository.findUserDetailByStatus(status);
return userDetail;
}
}
export const userService = new UserService();
UserController.js
import { userService } from '../services/user';
#RouterAdapter
class UserController {
#GetMapping("/getUserDetails")
getUserDetail() {
return userService.getUserDetail(true);
}
#GetMapping("/getUserDetailsTop10")
getUserDetailsTop10() {
return userService.getUserDetail(true).slice(10);
}
}
export const userController = new UserController();
app.js
import * as express from 'express';
import { userController } from './controllers/user';
const app = express();
app.set('view engine','hbs');
app.use(userController);
app.listen(3000, () => {
console.log("Started server on port : 3000");
});
I intentionally "warp" my js code to match with java style so perhaps you feel at home. Personally I don't write js this way, I prefer using function way more than class.
I did not include the implementation of two decorators (#RouterAdapter, #GetMapping) I used, that's a detail not related to the discussion. I can assure you this is doable in js. The only thing missing is that js doesn't have runtime method overload, so I have to name 2 diff controller methods.
The point I'm making here is that design patterns are mostly language agnostic. If you're familiar with the lang, you can always find a way to handle separation of concern well.
Now I intentionally use class in above example, but a function acting as a UserService doesn't make it less reusable. I don't see why you think "it doesn't scale".
I was asked to make API call to send data.
On Click in vue, I was firing this event
async facebookDataToSend () {
let campaignID = await this.$store.getters['CurrentInstance/id']
this.$axios.post(process.env.API_BASE_URL + 'faceeBookCampaign', { campaignID: campaignID }, { withCredentials: true })
},
But then, I was told to use API functions which already exsist in some xyz.js file.
My xyz.js file looks like this..
const rest = {
something: axios.create({
baseURL: process.env.API_BASE_URL,
withCredentials: true
}),
setClient: function (client) {
this.something = axios.create({
baseURL: process.env.API_BASE_URL,
withCredentials: true,
params: {
__somethingClient: client
}
})
this.client = client
}
}
Here, I am unable to comprehend how can I use this instance to make an api call So I viewed the code where they have already made the api call and saw something like this
const API = {
url: '/whateverHtml/',
method: 'post',
withCredentials: true,
data: {
'schemaType': 'something-event', // TODO FIXME
'field': 'description', // TODO FIXME
'html': this.model[this.field.key]
}
api.something.request(API).then(result => {
And I wasn't able to comprehend the code. For starters
What is request? I don't see my any method or property inside something in my rest variable
second why are they using withCredentials: true in their API object when they have already set up the property as true in rest object]
What are the pro's of using axios.create({ i.e what they are doing than what I initially did this.$axios.post(
request is a method defined by axios. Link to docs.
request allows you to make an HTTP call with any verb you want (POST, GET, DELETE, PUT). Most likely axios calls request from inside all the other helper methods (get, post), but this is an implementation details. One of the advantages of using request is that you don't have to hardcode the HTTP verb (POST, GET ...) and you can set it at run time depending on your input.
I see 2 reasons why they would set withCredentials:
setClient may or may not be called before something
for clarity: it's enough to look at the definition of something to realise that the client is using credentials and you don't need any extra information about how rest works.
I don't think the request for you to use something boils down to advantages of axios.$post vs axios.create. It's probably related more to how to organise your code.
Some advantages of using a separate module vs calling axios directly
when calling axios directly you are prepending base url all the time, when using a module for your REST API the base URL is tucked away and arguably makes your code easier to read
you can bake other options inside config and make sure they are used. For instance you may have an access token, the module can store that token and always added to any request. When calling axios by hand you need to remember this
you are decoupled from axios (to some degree)(1). When using a module you don't actually care if it's axios doing the requests or not.
You can add more API calls to the module that you can reuse in the future. I'm expecting xyz file to grow in time and your call to faceeBookCampaign to end up being a method on the rest variable. It makes more sense to end up using this.client and not something but this is up to the devs.
it keeps all the REST API calls in one place allowing you to build an SDK for that API, which as the project grows can have its own life cycle.
(1) I say that id decouples you to some degree because there are semantics that need to be kept so everything works. The returned object needs to have a request method that accepts a config object. The config need to conform to the same structure as the one that axios wants. But, you can always write an adapter for that, so you are actually decoupled from axios.
request here takes a config and returns a promise. I am guessing this approach is usually taken when you want to reuse a request object that is created using create (at least my sense).
I feel the request method is used to overwrite the initial configuration with new one defined in API. And, the double withCredentials should be an oversight.
OR, because API is defining a new config object, therefore when defined without withCredentials, it would overwrite the create's configuration.
Hence, it looks like its specified twice.
Is there any way I can trigger a policy on a specific request method (e.g. DELETE) rather than on specific routes?
I'd imagine something like this:
module.exports.policies = {
'DELETE *': 'isAdmin'
}
My goal here is to expose the blueprint api to admins only, so that I can keep it in production, as it's a very useful tool for allowing third party scripts to add extra functionality.
I'm on Sails 1.0 right now.
One way to do that might be to add the check for the request method to the actual admin policy, however that doesn't quite seem like the best solution to me.
You can override the blueprint for all models for a particular method. You can do this for DELETE by creating a file destroy.js in /api/blueprints/ and then adding your code for what you want to do when a DELETE comes through:
module.exports = function(req,res, next) {
if(ACLService.hasPermission(req.user.acl, 'admin')) {
//Ok to allow delete here
} else {
return res.unauthorized();
}
};
This is how I've done it in the past, but looking at the docs for the just released SailsJS 1.0:
https://sailsjs.com/documentation/reference/blueprint-api
You may need to add this hook for overriding blueprints in 1.0
https://www.npmjs.com/package/sails-hook-custom-blueprints
Here is one method that you can use, I am not claiming that it is the right way, but you can consider it:
You can write your own hook. How to do this: https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks
Basically here is the solution with a hook:
1 Create a hooks folder under your api folder.
2 In the hooks folder create another folder - the name will be the name of your hook (say my-hook).
3 In api/hooks/my-hook create a file index.js and in it put the following code:
module.exports = function myHook(sails) {
return {
routes: {
before: {
'/*': function (req, res, next) {
if (req.method.toUpperCase() === 'DELETE') {
return sails.hooks.policies.middleware.isadmin(req, res, next); // note - your policy function name must be called here with all lowercase, otherwise it will not work.
}
return next();
}
}
}
};
};
Then in your isAdmin.js policy you can check if your user is an admin and if not:
return res.forbidden();
if it is admin:
return next();
I would like to have a sub part in my application:
For instance, all requests send to www.example.com/backoffice/user should be redirected in my BackofficeUserController.js.
I ue sails.js, I know I have to do that with the config/routes.js, I just don't know how.
I tried this:
'/backoffice/:controller/:action?': {
controller : 'backoffice' + ':controller',
action : ':action'
}
But it doesn't works. Any idea?
The doc doesn't explains too much about dynamic routes. http://sailsjs.org/#!documentation/routes
This is actually a decent use-case for nested controllers. If you do sails generate controller backoffice/user, you'll end up with a controllers/backoffice/userController.js file corresponding to a controller class called Backoffice/UserController. All requests to /backoffice/user/:action will then be automatically routed to that controller.
All captured params get passed to the controller's action method on your request object. Maybe you should be more explicit when defining your routes or use your UserController as a proxy.
You could have backoffice users?
'/user/backoffice': 'UserController.backoffice'
or having a backoffice controller handle user requests
'/backoffice/user/:id': 'BackofficeController.user'
or (i'm not sure if controllers are global but you could require the controller from another controller and use its methods inside UserController)
module.exports = {
'/backoffice/user/:id': 'UserController.backoffice'
};
and then in your UserController
var BackofficeController = require('./BackofficeController');
module.exports = {
user: function(req, res) {
// Do something using BackOffice methods conditionally?
}
};
Many ways to achieve the same result. Not sure what the best approach is since I haven't run into this personally. But I would suggest sticking with Sailsjs conventions.