The Foxy forums are on the move!

We're in the process of moving our forums over to a new system, and so these forums are now read-only.
If you have a question about your store in the meantime, please don't hesitate to reach out to us via email.

How Syncing Passwords Should Work

charlie_mezakcharlie_mezak Member
in Help edited February 2012
I've read through the documentation many times, but I don't think I quite understand how account syncing for SSO is supposed to work.

Here's the theory I'm working with:

- I set up my rails app to use SHA1 encryption
- I set up foxycart to send the user hash and salt with SHA1
- when I receive a transaction data feed, find or create the user account in my app, and set the password hash and salt to the values from the feed
- the user should now be able to sign in to my app using the same password they used on foxy cart

Is that correct? If so, then I must be missing some configuration issue for my app's encryption method that's causing its generated hash not to match the one from foxy cart, but I don't know what that config issue might be.

Thanks!

Charlie
Comments
  • fc_adamfc_adam FoxyCart Team
    @charlie_mezak,

    Could you post your ruby code where you're working with the password?
  • @fc_adam,

    Sure. My code is pretty simple. If a customer record with the email exists, it's updated with the attributes pulled form the data feed. If not, a new customer record is created. password fields are updated individually.

    I've verified that the resulting record's encrupted_password (hash) and password_salt fields match what I get from the feed.
    if customer
              puts "Customer record found!"
              if customer.update_attributes attributes
                customer.update_attribute :encrypted_password,  attributes[:encrypted_password]
                customer.update_attribute :password_salt,       attributes[:password_salt]
                puts "record updated!"
              else
                puts "record update failed!"
              end
            else
              puts "Adding new customer record!"
              customer = Customer.new attributes
              customer.password = "dummy_password"
              customer.password_confirmation = "dummy_password"
              if customer.save
                customer.update_attribute :encrypted_password,  attributes[:encrypted_password]
                customer.update_attribute :password_salt,       attributes[:password_salt]
                puts "customer created!"
              else
                puts "customer creation failed"
                puts customer.errors.full_messages
              end
            end
    

    If my devise.rb initialization file, I've set the encryptor to :sha1:
    # ==> Configuration for :encryptable
      # Allow you to use another encryption algorithm besides bcrypt (default). You can use
      # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
      # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
      # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
      # REST_AUTH_SITE_KEY to pepper)
      config.encryptor = :sha1
    

    Stretches can also be configured in this file. It was originally set to a default of 10. I've also tried a value of 1.

    You can also configure a pepper value in the config file, but I haven't done that.

    Here's the source code from the SHA1 module of the Devise gem that I'm using for authentication:
    require "digest/sha1"
    
    module Devise
      module Encryptors
        # = Sha1
        # Uses the Sha1 hash algorithm to encrypt passwords.
        class Sha1 < Base
          # Generates a default password digest based on stretches, salt, pepper and the
          # incoming password.
          def self.digest(password, stretches, salt, pepper)
            digest = pepper
            stretches.times { digest = self.secure_digest(salt, digest, password, pepper) }
            digest
          end
    
        private
    
          # Generate a SHA1 digest joining args. Generated token is something like
          #   --arg1--arg2--arg3--argN--
          def self.secure_digest(*tokens)
            ::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
          end
        end
      end
    end
    

    Thanks!
  • lukeluke FoxyCart Team
    Hey Charlie. What store is this for? Are you seeing errors in the error logs for your store?
  • collierwine.foxycart.com. I've received errors, but not related to this issue. The above code works fine, produces no errors. The password just doesn't seem to work on my end.
  • lukeluke FoxyCart Team
    Hmm... so I'm confused about this statement:
    I've verified that the resulting record's encrupted_password (hash) and password_salt fields match what I get from the feed.

    If they match exactly... why wouldn't it work? Might be a question for a rails developer. Definitely odd if the data itself matches. If it were me, I'd run some tests creating passwords on your end (and in FoxyCart) and comparing things. From there, I'd walk through the code that validates the passwords and see what's going wrong.
  • Thanks Luke. Just wanted to make sure I was doing things right on the FC end of things. I've tried things like creating a password in rails and copying the resulting hash and salt to a different account. That worked.

    Perhaps this is the problem:

    The docs say that the "method" of SHA1 for FC is
    sha1($password.$salt)
    

    but the code I posted above uses:
    # Generate a SHA1 digest joining args. Generated token is something like
    #   --arg1--arg2--arg3--argN--
    def self.secure_digest(*tokens)
        ::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
    end
    

    I wasn't sure what the "method" int he docs was referring to, but if it's the way that the salt and password are combined to produce the hash, then this could be the discrepancy I'm looking for. I'll figure out how to replace the above with my own code without the "--"s

    Will post back if this solves it.

  • That was it! It's working. Had to take a closer look at the encryptor and rewrite the above to:
    class Sha1Foxycart < Base
          def self.digest(password, stretches, salt, pepper)
            self.secure_digest(password, salt)
          end
    
        private
    
          def self.secure_digest(*tokens)
            token_string = tokens.flatten.join('')
            ::Digest::SHA1.hexdigest(token_string)
          end
        end
    
  • lukeluke FoxyCart Team
    Thanks so much for posting back. If you get things going well, please feel free to add your code to our wiki integrations page.
  • @luke

    One thing is still confusing me about the user experience when checking out as a new user. They start on my site and go through foxy cart to make a purchase. If in so doing they set up a new account with password, my CMS knows about it via the data feed, but when the new user clicks "continue" on the receipt page to return to my site, I'd like them to be logged in. It seems strange to have them create an account and immediately have to log into it, especially since from the user's perspective they've never left my site.

    This seems like it should be folded up into the SSO scheme somehow, but I don't see where it is.

    Thanks.
  • sparkwebsparkweb Member, Integration Developer, FoxyShop, Order Desk
    @charlie_mezak - you can create their account in your system via the datafeed, but how would the user get logged in? They need to set a cookie right, and they've never been logged in on your server before.
  • charlie_mezakcharlie_mezak Member
    edited March 2012
    @sparkweb I thought that the link back to my server could include some authentication info that I could use to recognize the user and log them in automatically. Sounds like not.
  • sparkwebsparkweb Member, Integration Developer, FoxyShop, Order Desk
    The problem with that is that the info would be publicly available. The user could copy and paste that url to a friend who would then be logged in as them. I think it would have to have a timeout and maybe do IP checking or something. You might be able to use jQuery to append some of this information to the continue link from the receipt page.
  • If a random token were generated with new users and sent to my CMS with the data feed, then that token could be included when the user returns to my site. My CMS could make the call (based on time or other things) about whether to accept it and log the user in.

    But anyway, I just wanted to know if there was a built-in way to handle this. Since there isn't, I won't worry about it too much. It's not a requirement for me.
  • brettbrett FoxyCart Team
    @charlie_mezak, good idea there. We'll ticket it up, but on the surface it sounds doable.

    ...I wonder if the fcsid would work for that... or maybe the fcsid value salted with your API key and hashed...
  • brettbrett FoxyCart Team
    @Luke just (in an accidentally public whisper, sorry Charlie) mentioned using the order_id instead of a unique token. If you tie it to the IP address I think that'd be "secure enough" for most purposes. Specifically, the only attack vector that comes to mind would be if somebody could eavesdrop on the traffic and see the "continue" link. The attacker could at that point take the link and hit it, and they'd likely (based on the attack vector) be coming from the same IP. But if the network was already compromised in that way your site's sessions could likely be stolen anyway.

    So like @sparkweb suggested (which is what we've suggested in the past), your best bet probably would be to grab the order_id on the receipt, append it as a querystring to the "continue" link, then just hit the API when a user hits that page and if the IP matches and it's within a few minutes of the initial transaction, log them in. Else, prompt for login.

    That sound workable? I know you said it's not a requirement, but I agree that it's something nice to have.
  • @brett

    Sounds awesome. When I have some time I'll give it a try and report back.

    Thanks!
Sign In or Register to comment.