2

Can't seem to create a functional way to insert a user from Java for Devise. Currently there are these fields: "_id", "access_level", "confirmation_sent_at", "confirmation_token", "confirmed_at", "email", "encrypted_password", "sign_in_count"

I am able to insert a document that counts as a user. The problem is that when I go to: http://www.mysite.com:3000/users/confirmation?confirmation_token=TOKENHERE

I get a message saying that it's invalid.

EDIT 1: When I resend confirmation instructions for this user (WHICH GENERATES A NEW TOKEN), the user can be logged into. This confirms my doubts about the token being the problem. How can I port Devise's token generator to Java?

EDIT 2: When I register on site, it says I should check for a confirmation link. However, if I go into the Mongo shell, manually take out the confirmation token and paste it to site.com/users/confirmation?confirmation_token= then it doesn't work! However, if I actually use the confirmation link I was sent, it works. How can I make a VALID token, all from Java. Please help!

njny
  • 559
  • 2
  • 14

2 Answers2

0

For this quoestion you should refer to this stackoverflow answer and to the Rails API of protect_from_forgery.

The short answer is to disable forgery protection in your controller, but this makes your application vulnerable to CSRF attacks:

skip_before_action :verify_authenticity_token

The better way would be to authenticate with a JSON or XML request as these requests are not protected by CSRF protection. You can find a solution for devise here.

Edit

Monkey patch devise to save unencoded confirmation token. In your config/initializers/devise.rb

module Devise
  module Models
    module Confirmable
      def generate_confirmation_token
        raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
        @raw_confirmation_token = raw
        self.my_unencoded_column = raw # Patch
        self.confirmation_token = enc
        self.confirmation_sent_at = Time.now.utc
      end
    end
  end
end
Community
  • 1
  • 1
marthoff
  • 31
  • 2
  • Hello, I think you might be misunderstanding my question. I'm not having a problem with form authentication tokens, my problem is registering users into Devise via Java while also using the confirmable module. I currently can successfully register a user from Java, but cannot generate a token that is valid. Here's what I know currently: When a User signs up, and is sent a token to their email, that token is not the same as the token stored in the field "confirmation_token" in the users document. Devise apparently has some way of making unique tokens, I need to port this to Java. – njny Nov 18 '13 at 22:17
  • Hello, please excuse the misunderstanding.What I can find out about this topic is that devise generate the token by calling: OpenSSL::HMAC.hexdigest(@digest, key, raw) in [src](https://github.com/plataformatec/devise/blob/master/lib/devise/models/confirmable.rb) line 221. The problem is that devise does not store the blank key in the database just the encoded one. Maybe it is possible to monkey patch the class. – marthoff Nov 19 '13 at 13:25
  • My devise initializer file doesn't look like that. Where would I put that? I'm new to Rails, sorry! Additionally, what if I need to store the user through Java? I can't really call that method from Java, would this allow the token generated in Java to work? – njny Nov 19 '13 at 22:20
  • Ok. If you want to insert the record from Java side you need to create the raw key and encrypt it with an java version of OpenSSL::HMAC.hexdigest(@digest, key, raw), but this seems very complicated from my side. Sorry, no further idea. – marthoff Nov 20 '13 at 13:25
  • Ok! I finally think I understand what must be done. http://pastebin.com/B6pDDcbP I registered a user from Devise, and am trying to get to the token in the DB by encrypting the one sent out to the email. Unfortunately, Devise generates a key from the column number as a salt, and then further has a way to prevent re executing the generator. Any idea how to make a key the way they do? Here: https://github.com/plataformatec/devise/blob/d56641f514f54da04f778b2a9b816561df7910c2/lib/devise/token_generator.rb – njny Nov 21 '13 at 21:41
  • Ok maybe final solution. Change the method of devise. As mentioned above you can Monkeypatch the devise class, which generates the key. So you can simple change the generation of code in devise. Of course the is only possible if you have access to the rails code. Simple try to insert code like written above into an devise.rb file in your config/initalizers path. – marthoff Nov 23 '13 at 13:12
0

In case anyone else finds themselves trying to get a java or scala app to coexist with a rails app, I hacked up the following. Its in scala but uses java apis so should be easy to read. As far as I can tell it replicates Devise's behavior, and if I hit the confirmation link in the rails app with the raw token rails/devise generates the same encoded string.

import java.security.spec.KeySpec
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
import javax.xml.bind.DatatypeConverter
import java.util.Base64 

// copy functionality from Rails Devise
object TokenGenerator {

  // sample values 9exithzwZ8P9meqdVs3K => 54364224169895883e87c8412be5874039b470e26e762cb3ddc37c0bdcf014f5
  //              5zNMi6egbyPoDUy2t3NY => 75bd5d53aa36d3fc61ac186b4c6e2be8353e6b39536d3cf846719284e05474ca

  private val deviseSecret = sys.env("DEVISE_SECRET")
  private val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
  val encoder = Base64.getUrlEncoder()

  case class TokenInfo(raw: String, encoded: String)

  def createConfirmationToken: TokenInfo = {
    // copy behavior from rails world. Don't know why it does this
    val replacements = Map('l' -> "s", 'I' -> "x", 'O' -> "y", '0' -> "z")

    // make a raw key of 20 chars, doesn't seem to matter what they are, just need url valid set
    val bytes = new Array[Byte](16)
    scala.util.Random.nextBytes(bytes)
    val raw = encoder.encodeToString(bytes).take(20).foldLeft(""){(acc, x) => acc ++ replacements.get(x).getOrElse(x.toString)}
    TokenInfo(raw, digestForConfirmationToken(raw))
  }

  private def generateKey(salt: String): Array[Byte] = {
    val iter = 65536
    val keySize = 512
    val spec = new PBEKeySpec(deviseSecret.toCharArray, salt.getBytes("UTF-8"), iter, keySize)
    val sk = factory.generateSecret(spec)
    val skspec = new SecretKeySpec(sk.getEncoded, "AES")
    skspec.getEncoded
  }

  def sha256HexDigest(s: String, key: Array[Byte]): String = {
    val mac = Mac.getInstance("HmacSHA256")
    val keySpec = new SecretKeySpec(key, "RAW")
    mac.init(keySpec)
    val result: Array[Byte] = mac.doFinal(s.getBytes())
    DatatypeConverter.printHexBinary(result).toLowerCase
  }

  private def getDigest(raw: String, salt: String) = sha256HexDigest(raw, generateKey(salt))

  // devise uses salt "Devise #{column}", in this case its confirmation_token
  def digestForConfirmationToken(raw: String) = getDigest(raw, "Devise confirmation_token")
}
corn_dog
  • 161
  • 6