The only other thing I would note is that calling it a “long” token makes it sound like some sort of magic is going on, but if you look at the implementation it’s actually quite simple.
Generating the masked token is literally equivalent to this:
token = Base.url_encode64(:crypto.strong_rand_bytes(18))
mask = Base.url_encode64(:crypto.strong_rand_bytes(18))
masked_token = Base.url_encode64(:crypto.exor(token, mask)) <> mask
So the masked token is roughly twice as long because the mask is simply concatenated to the token! That way, on the receiving end, the mask can be used to decode the token.
It’s also interesting that the token ends up being base64-encoded twice, which I had not noticed until I typed it out. Seems slightly suboptimal, but I would imagine fixing it wasn’t worth changing the rest of the code.
I’m relatively new to SSO. I’ve gotten that callback to work and I can identify the user. I’m trying to add CSRF protection to the callback, so I know it is coming back from the original request I sent to login. I thought I’d be able to use get_csrf_token() when displaying the login page, then when I get the callback, I could call get_csrf_token() and I would see the same token in the other controller. It appears they are different. As I understand this, I want the tokens to be the same to verify that the call “originated” from my login page. Furthermore, I have to then render a live_view page, this thread still leaves me confused on the first and 2nd steps. The tokens are different, and there is a different _csrf_token" => “token” when the web socket triggers as well as a new _csrf_token" => “4th_token” when the live view mounts.
You have not specified what you’re implementing but I’ll assume it’s OAuth2.
The purpose of Plug.CSRFProtection is to detect cross-origin requests (using an antiquated method). The auth callback is cross-origin by design, so it’s not going to work there. The spec has a section detailing how to prevent CSRF using the state parameter. CSRF protection is part of the spec.
Yes, please excuse me, I’m using OAuth2. Thank you for sharing the spec. I understand I can generate the state parameter using what the spec suggested. Once my other controller gets the callback, what is the proper way to have stored the state so I can check if it matches?
You would just stick it in a cookie or in the session (which is probably a cookie). It’s written to be broad because the client is not necessarily a browser.
# Login
state = Base.url_encode64(:crypto.strong_rand_bytes(64))
put_session(conn, :oauth_state, state)
# Callback
state = get_session(conn, :oauth_state)
valid? = (state == Map.fetch!(query_params, :state))
If you are implementing this you should definitely read that spec in full before proceeding.
Hi,
Thank you, I ended up implementing it basically exactly how you said, just sticking the state in the session. I’m going to go through the spec and make sure I’m in compliance. Thanks again for responding and sharing the spec.
1 Like