How does Laravel interally create certain parts of the verification email? - laravel

When creating a verification email with Laravel this is how the link can end up looking:
.../verify/1/3f4f10efdbac36ec6892bb3572ac6683ff663ad8?expires=1641580767&signature=babf2d50deb610a551d0477132193abb595d8664b56a9074c38f5b3789933ad
After the "verify/1/" there seems to be some hash of length 40.
The last query parameter "signature" has a hash of length 60.
My questions are: How are these hashes created? Which hash function is used and what is the input string? Also what is the purpose of those parts?

1- The first part after the verify/1/ is the sha1 of the registered user email.
We use this to make sure we validate the same email we have in the db and the one the user registered with.
2- The last part of the url is a sha256 signature to make sure the url is not altered by a malicious user. Any modification to the url will make the signature fails. Note that the signature is checked with the Laravel Signed middleware
So it is basically security measures to prevent malicious user.
For more informations:
The generated link will be in the notification class here: src/Illuminate/Auth/Notifications/VerifyEmail.php
Once the user clicked the link, it will be processed and checked in the file below: vendor/laravel/ui/auth-backend/VerifiesEmails.php

Related

How to assert If the signature is valid in the url - Laravel, TDD

I have a rating & review form which is used by the customers to submit their reviews. This form can be accessed using a url even if they are not signed-in to the platform.
I use signed routes to prevent anyone from submitting the form. The url is shared to them via email. The url looks like below:
http://localhost:8000/review?order_id=12345&signature=c95c7d59e240d97c5d4ceaa0fe4d75a9a100871a0d36b8a997f5a4c4f4567777
If someone tries to submit the form without the signature or invalid signature or invalid order_id, an error is thrown. Each signature is unique to an order_id.
I create signed route using below code:
$signed_url = URL::signedRoute('add-review', ['order_id' => $newOrder->id], null, false);
I am writing a test case to check if the signature exists and is valid but I can't seem to find an assert that I could use to check if signature is valid.
The only asserts I found which is for signed route is:
assertRedirectToSignedRoute
https://laravel.com/docs/9.x/http-tests#assert-redirect-to-signed-route
I do not redirect user to a signed route, instead I store the url in the database and attach the url when an email is sent.
The code I use in the controller to do the check is
if (! $request->hasValidRelativeSignature()) {
throw new HttpResponseException(response()->json([], 403));
}
Why not fetch the url to the database and use assertEqual() to test if the signed url and the url related to the order ID is equal

How does Djoser account verification system really works under the hood?

So I'm currently in an attempt to make my own account verification system and I'm using some parts of Djoser as a reference. let me try to walk you to my question
Let's say you're to make a new account in Djoser app
you put in the information of your soon to be made account including email
submit the form to the backend
get an email to the whatever email account you put in earlier to verify your account
click the link in your email
get to the verify account page
now in this page there's a button to submit a UID and a token and both of those information lies in the URL.
My question is:
What are those tokens? is it JWT?
How do they work?
How can I implement that in my own projects without djoser?
The answers to your questions are immersed in the own code of djoser.
You can check djoser.email file and in the classes there, they are few methods get_context_data().
def get_context_data(self):
context = super().get_context_data()
user = context.get("user")
context["uid"] = utils.encode_uid(user.pk)
context["token"] = default_token_generator.make_token(user)
context["url"] = settings.ACTIVATION_URL.format(**context)
return context
So get the context in the class where is instance, and in this context add the 'uid' (this is basically str(pk) and coded in base64, check encode_uid()), the 'token' (just a random string created with a Django function from your Secret key; you can change the algorithm of that function and the duration of this token with PASSWORD_RESET_TIMEOUT setting) to use temporary links, and finally the URL according the action which is performed (in this case the email activation).
Other point to consider is in each of this classes has template assigned and you can override it.
Now, in the views, specifically in UserViewSet and its actions perform_create(), perform_update() and resend_activation(), if the Djoser setting SEND_ACTIVATION_EMAIL is True, call to ActivationEmail to send an email to the user address.
def perform_create(self, serializer):
user = serializer.save()
signals.user_registered.send(
sender=self.__class__, user=user, request=self.request
)
context = {"user": user}
to = [get_user_email(user)]
if settings.SEND_ACTIVATION_EMAIL:
settings.EMAIL.activation(self.request, context).send(to)
...
The email is sent and when a user click the link, whether the token is still valid and uid match (djoser.UidAndTokenSerializer), the action activation() of the same View is executed. Change the user flag 'is_active' to True and it may sent another email to confirm the activation.
If you want code your own version, as you can see, you only have to create a random token, generate some uid to identify the user in the way that you prefer. Code a pair of views that send emails with templates that permit the activation.

Laravel Encryptable Trait Failing Authentication

I'm running into trouble with authentication handling in my Laravel 5.5. I have installed an Encryptable trait according to this post here. I then used the authentication generator to establish the base routes, views and handler.
I can successfully register new accounts and visually see that all of the data is encrypted, but I cannot successfully authenticate through the login screen.
This seems to be failing during the Auth::attempt($credentials) call. My troubleshooting is pointing to the encryptable trait because when I comment that section out, the authentication works fine.
Can someone offer insight as to how to handle authentication using this method of model encryption?
I have attempted disabling encryption for the username field, but this didn't seem to help. The password field was never being encrypted, becasue it is being hashed by bcrypt.
1st Edit:
So, with an understanding of how traits work... The Encryptable trait seems to be overloading the getAttribute/setAttribute functions. This would mean that Eloquent's querying functions like where, find, etc. will just be "looking at" encrypted values.
2nd Edit:
The source code provided for the Encryptable trait was not returning proper values for unencrypted values. This was changed and authentication was restored. To those using the same code snippet, in the get_attribute() function, change the else block so that it return $value;.
I appreciate all insights,
Dan
This form of encryption will void your ability to search the table for the encrypted fields. You won't be able to reproduce the same string because Laravel uses a random iv when producing encrypted data. An IV, or initialization vector, serves a similar purpose as a salt in hashing, to randomize the stored data.
Due to this randomization of data, you wouldn't even be able to search your table by re-encrypting the search data:
User::where('email', Crypt::encrypt('email#email.com'));
// won't find anything even if an encrypted value of email#email.com exists
Running in an interactive shell allows you to see encrypt returns a completely different value on subsequent runs:
>>> json_decode(base64_decode(Crypt::encrypt('email#email.com')))->value
=> "zpA0LBsbkGCAagxLYB6kiqwJZmm7HSCVm4QrUw6W8SE="
>>> json_decode(base64_decode(Crypt::encrypt('email#email.com')))->value
=> "VKz8CWVzR66cv/J7J09K+TIVwQPxcIg+SDqQ32Sr7rU="
Therefore, you may want to be selective about what you actually encrypt. Encrypt things that are sensitive and you wouldn't use to lookup an entity. This could be something like social security numbers, government IDs, credit card numbers, and bank account numbers.

Laravel 5.4: Password reset token not the same as email token

I have a slight problem after upgrading to laravel 5.4
When i do a password reset, the email gets generated and sent perfectly, however the token it saves to the user record in the database is as follows:
$2y$10$N0WFuqEkEIFto.CazxYLdOUmY1X9tBHfvDn8iWKUdlq2W9uOc00Ku
But the token it sends to the user to do a password reset is:
bc1c82830bc8ad1356aa5e2a2a5a342ae6c6fabd385add503795cca1a1993e15
My question is why are the two tokens different. and how do i perform a check now to validate if the token exists in the database as i need to get the email address to post to the reset controller.
Thanx in advance.
Token you store in database is hashed same as your password column in users table.
However the token you recieve is not hashed. Thats why they are different
Due to get this password ;
$2y$10$N0WFuqEkEIFto.CazxYLdOUmY1X9tBHfvDn8iWKUdlq2W9uOc00Ku
you have to do
Hash::make('bc1c82830bc8ad1356aa5e2a2a5a342ae6c6fabd385add503795cca1a1993e15');
And you cannot revert this process backwards.
The token in the database is encrypted with Bcrypt. That's why it is different in the database.
The token will still work when you use it.
The token it stores in the database is the same string, but hashed with bcrypt, a secure and adaptive algorithm based on the Blowfish cipher.
You can see the documentation for the vanilla PHP password_hash() function to see how it's built, and the password_verify() function to verify that the hashed string is valid against an unhashed version of it (what is sent to the user, in this case).
Laravel Hashing
Laravel includes its own hashing objects and facades which are documented.
To create a hash:
$string = 'Hello world.';
$hash = Hash::make($string);
To verify the hash against a plain string:
if (Hash::check($string, $hash)) {
// The passwords match...
}
Note: In Laravel 5.4, the email token changed from SHA256 to bcrypt in an undocumented change (as issue #18570 shows), so bear that in mind if you are upgrading from Laravel 5.3 or lower.

How to handle encrypted URL's in rails?

I am sending email to user, in that email one link is there to redirect that user to rails application. I want that link to be in encrypted form with domain name for example:
https://www.domain_name.com?hdurstihnzdfalgfgdfhdrbnhduolsasrtyumyrtyr
when user click on this link, he should directly redirect to controller method we specified in that URL which is not visible.
Controller and methods given in URL may vary according to user.
So my question is how we can do this in rails.
If I encrypt controller name, method name and parameter we passed. How routes file come to know where to redirect this URL? How to decrypt this in routes file and redirect internally to decrypted URL?
Life will be easier if you can do a slight modification to your url, something like:
https://www.domain_name.com/enc/hdurstihnzdfalgfgdfhdrbnhduolsasrtyumyrtyr
Then you can create a route for that path to redirect where you want.
get '/enc/:encoded_path' => "controller#action"
This would give you access to params[:encoded_path], which would equal hdurstihnzdfalgfgdfhdrbnhduolsasrtyumyrtyr in this case. From there, you could decode in the controller and then redirect however you want.
That's not the right approach. Here's what you can do instead:
Create a new controller action, say for instance, activate.
def activate
activation_token = params[:auth_token]
....
your logic to do whatever with this token
end
Create a corresponding route:
match '/activate' => 'your_awesome_controller#activate'
Now, when you email your users, I'm guessing you're sending some sort of activation token. If not, create two new fields in your users table:
activation_token:string
activated:boolean
Use some unique string generation algorithm to generate your activation_token and email it to your user:
yourdomain.com/activate?auth_token=user.activation_token

Resources