New Discussion

Notifications

You’re not receiving notifications from this thread.

Handling Subdomains and Multitenancy From Scratch Discussion

55
General
Chris Zempel Chris Zempel

This is my favorite episode thus far. The expansion on top of Rails is so intuitive, never thought how useful separating accounts from users would be.

Hopefully there's a simple answer to this. When I'm making AJAX requests using multi-tenancy that works like this, what's the safest way to get information about the subdomain and the user into the request? Right now, my thought is to stick them directly on the page somewhere (lets say hidden fields) then pull that information out with JS to populate the rest of the form.

My worry is, somebody knows a subdomain they want to create a blog post on. So they just clack in different user_id's until they find the right combination.

Is there a different/better way to do this? Or can I somehow figure out what session the user is actually in (maybe via the csrf token?) & then validate against that before doing anything back-end? Or is this **the reason** for the csrf token, and if so, how much protection is cooked in?

This is kind of a dark area of understanding for me right now, so any insight would be appreciated!

I believe you're over thinking the problem a bit around how the authorization would work. The user who is logged in cannot spoof their user because their ID is encrypted in the session. That User ID gets sent over every AJAX request so you will authorize that user's activity against the subdomain they POST'd too. Each account will need a list of users authorized to make changes on it that you authorize against. This prevents you from spoofing yourself as another user. You will basically treat them as having access to any account they are authorized on which is okay.

That said, you could modify the subdomain in the HTML, but it wouldn't matter because you would only be able to make changes on the authorized subdomains provided you set up your authorization correctly.

Kohl Kohlbrenner Kohl Kohlbrenner

is build_account a helper for something like User.account.build ? Is there a specific name for it/link to documentation

Yeah, it's one of the methods generated by the has_one association. The docs show it as "build_association" and "create association". http://apidock.com/rails/Ac...

Kohl Kohlbrenner Kohl Kohlbrenner

@excid3:disqus how would you go about eliminating subdomains like www and admin from being used? Something like a before create action that says subdomain != 'www' or 'admin' ?

Also, how does Rails know to route to pages with a subdomain? In the routes file you never explicity told rails you were using a subdomain. I feel like your code should break when you did example.lvh.me.com/users/si... and then it redirected you to the example.lvh.me.com correctly. To me, all subdomain is is an attribute on the Account model.

Kohl Kohlbrenner to eliminate subdomains like www and admin I do something like this : Account.find_by(subdomain: request.subdomain) if (request.subdomain.present? && !["www", "admin"].include?(request.subdomain))

Also you don't have to tell your routes that you are using subdomains, the web server know about your url and it's enough to get the subdomain by using the "request" object which is provided by Rails.

How is the user_id on the account table being set?

The nested form for the user also creates the account using accepts_nested_attributes_for. The user_id automatically gets set because it is created through the association.

Nice tutorial. please how do i register a user with the same email address but different subdomain, like Slack?

In that case, the User models would belong inside the tenants.

Is there anything special that needs to be done to continue to use devise helpers like current_user or user_signed_in? inside of a multitenant app?

Nope, that should still work out of the box. Server side will just continue to set cookies, but will only match users available for the current tenant.

I created this Multi-tenant app, just to see how things work. But while creating new post I am keep getting error (undefined method `posts' for nil:NilClass ) I spent 3-4 hours debugging but no luck. Any help much appreciated!
Error is in
def index
@posts = @account.posts.all
end

Has anyone had any luck getting this to work with rails 5? Puma seems to have some issues that i can't quite figure out. Running the server under 'rails s -p 3000 -b lvh.me' allows me to access the server correctly, but it's showing the subdomain as 'subdomain.lvh.'

Somewhere in my debugging of this i had switched the action_dispatch.tld_length to be zero rather than the default of 1. Reverting back to 1 fixed the problem.

Just a heads up that the code for devise_parameter_sanitizer changes a bit with Devise 4 and up. It should now be devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :password, :password_confirmation, account_attributes: [:subdomain]])

I forgot that changed recently, great reminder. Thanks for sharing that!

Thank you so much for this! I ran right into this error bad!

Travis Glover Travis Glover

Is there a way to make the account available in a initializer or environment file? For instance I would like to use separate action mailer settings in environments/production.rb, and different braintree authorization settings in initializers/braintree.rb. Because the account is being set in the application controller, it is not available to be used as a conditional in these files. Is there any way to achieve this this? Maybe I can set these attributes in another file that is below the application controller?

I am using Rails 5 API with Devise Token Auth and Angular 2 with Angular2-Token as my front end. I keep getting an error saying "Unpermitted parameters: confirm_success_url, registration" and it fails without saving to the DB saying "422 Unprocessable Entity". I have tried adding these to permitted parameters and then get an error saying "ActiveModel::UnknownAttributeError (unknown attribute 'confirm_success_url' for User.)" and it fails with "500 Internal Server Error". I have removed "confirm_success_url, registration" from permit as I know that this is probably not the solution but am confused on how I should move forward? Sorry if this is a silly question as I am new to some of these technologies but any help moving forward would be appreciated.

Is there a way to make devise user session shared across all subdomain?

This worked for me.

In /config/initializers/session_store.rb

Rails.application.config.session_store :cookie_store, key: '_myapp_session', domain: {
  production: '.myapp.com',
  development: '.lvh.me',
  test: '.lvh.me'
}.fetch(Rails.env.to_sym, :all)
when following the video i was getting the error:
undefined method `for' for #<Devise::ParameterSanitizer:0x007fd1715018e8>

per this stackoverflow page (https://stackoverflow.com/questions/37341967/rails-5-undefined-method-for-for-devise-on-line-devise-parameter-sanitizer), I had to update the configure_permitted_parameters method (in application_controller) to this
def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up, keys: [ :email, :password,
                                    :password_confirmation, account_attributes: [:subdomain] ])
end
and then everything worked fine. hope this helps
What is the ideal way to scope Devise users by subdomain? i.e. Devise user can only log in to subdomain that created the user
I did. I've been unsuccessful to implement with the Account model. I'm switching to the Apartment gem. Thanks for the quick reply.

@chris That wiki is talking about scoping based on a string attribute on the user model, right?

So we would have to set up the system to user the account model reference, which isn't set on the user model here, because it is a has_one relationship.

Big piece of the puzzle missing here.

Any solution to this? based on the screencast here? Even if the users email isn't scoped with the subdomain but when you login it redirects you to your subdomain and creates the session would be good. Any ideas @chris?

Hi @Chris i have this error and i don't know why . i have a project model instead of a post model
When i visit the root_path i have this error
undefined method `projects' for nil:NilClass

project controller
  def index
    @projects = @account.projects.all 
  end
.....  def set_project
      @project = @account.projects.find(params[:id])
    end
....
help neeed
Hey! Would need to see more code before being sure if this is correct - but the error maybe happening because @account is not being set. Can you do a binding.pry and check the if @account is set propery. 

I'm having the same problem as this user. Based on the logs, I think you're right, the account is not being set. Here is the code from the application_controller.

class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
#before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :set_account

def require_account!
  redirect_to root_url(subdomain: 'www') if !@account.present?
end

def set_account
  @account = Account.find_by(subdomain: request.subdomain)
end

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up, keys: [:tos,
   account_attributes: [:subdomain]]) 
end

end

How else would I set the account?

Thanks in advance.

it's really hard getting respond here 

I am trying to set up after_sign_in_path_for and I am having some trouble with it. When the user signs in to localhost:3000 I would hope it would be able to send them to subdomain.lvh.me:3000/posts but it just keeps them at localhost:3000
This is my method

def after_sign_in_path_for
posts_url
end

Does anyone know how I should do this? I have already followed the direction on the devise page "How to scope login to subdomain. I am having the user log in on another page and when they log in, I want them to be directed to the post index page.

Hey Chris, before diving into the code side of things is there anything now after looking back that you would advise on with this approach? After chatting with Heroku, they advised against multiple schemas and to take a column scoped approach.

Hey Taylor, I agree that you should definitely use columns. I'm actually working on this functionality for Jumpstart Pro as we speak. 😜

Nice! Funny timing, with this I'm hoping to pair it with some sort of CNAME domain alias pointing to offer "Stores" for low-traffic accounts. Anything major that you're finding in Rails 6 with this approach or am I safe to hop in and start?

Hey Chris, I'm wondering how could I set one account for more than one user (owner).

Hey Chris,
I love your videos and I'm planning on subscribing again to Go Rails. Is there any change you can build a tutorial on how to build a full CRM application with a single database (row-level approach)?
I'm learning to implement rails as a backend and react in the frontend. Maybe you're more comfortable building a full Ruby On Rails application instead of using react and that's fine by me. I'm just very eager to learn how to build a full CRM application, as an example the idea I have been struggling with is to build a management app that can have many admins and each admin has many users, users belong to admin, users can has many projects through a join table and only the admin can create new projects. There are some resources out there but most are outdated and using the apartment gem which no longer supports the new versions of Rails.

Hey Marlon!

We're using the acts_as_tenant gem in JumpstartRails.com for row-level multitenancy. It works pretty well and will automatically set your tenant ID when querying and saving records. You end up building your app like a regular app that way which is nice.

I wouldn't recommend Apartment anymore because it's not row-level tenancy. It's far more common to see row-level being used.

Wow! Thank you for you prompt response, Chris! I'm reading the acts_as_tenant gem at the moment, would you be willing to share a walk-through tutorial/resource you think would be helpful for beginners like myself to get started with it?

Wow! Thank you for you prompt response, Chris! I'm reading the acts_as_tenant gem at the moment, would you be willing to share a walk-through tutorial/resource you think would be helpful for beginners like myself to get started with it?

Recorded a video on it. I'll be publishing it in the next week or two. πŸ‘

Uploaded it to YouTube so you could see it early. 😎

https://www.youtube.com/watch?v=5KuTzT_MWoM

I just finished watching the video. Thank you for sharing, Chris! Do you have any plans on creating a new course using this gem? I've been looking all over the internet and most resources are outdated from 2-6 years ago on building a CRM application using the apartment gem. If you built a whole CRM application using this gem I will be the first in line to get it! I really enjoy your videos and your explanations. Another thing missing out there too is building a CRM application from scratch without the gem and it would be incredibly informative and useful for beginners to have that walk-through without the gem and see how those associations and calling are being made. Amazing content, Chris. Be well and stay safe.

Hello, do you intend to do a very detailed tutorial of actes_as_tenant ?
Tank's :)

I would love to see this also!

This is great, thank you.

I'm wondering, just to round out the initial Wordpress example, can you briefly describe what is needed to be done to land on lvl.me (like Wordpress.com), login, then get redirected to your subdomain's personal landing page?

Maybe a "Handling Subdomains and Multitenancy From Scratch" part 2? It's kind of a fun topic. :)

Here's my code from this video in Rails 7: https://github.com/robault/CustomSubdomains

Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 88,834+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.