How does Djoser account verification system really works under the hood? - django-rest-framework

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.

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 Laravel interally create certain parts of the verification email?

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

Dynamically changing JWT subject field

I successfully implemented JWT as a authentication filter in my web application. When user's login is successful, I am creating a new JWT and assigning userName in the sub field of JWT.
In the subsequent request's I am using userName in the JWT sub field to identify the user. But what if the user changes his userName in the update section of the application. Is there way, I can update the value of sub field in JWT ?
What I am thinking!
I am thinking of getting the existing JWT in the RestController and after updating the userName, I will update the JWT with new userName and again send back to the client. Is this fine or is there a better approach?
I think I should refresh the token after update is done and send back the refreshed token back to client.
#RequestMapping( value = "/account", method = RequestMethod.POST )
public ResponseEntity<?> updateAccount( #RequestBody UserDetailsBean userDetailsBean, HttpServletRequest request,
HttpServletResponse response )
{
try
{
UserAccessDetails accessDetails = getLoggedInUser();
UserDetailsBean updatedUserBean = userService.updateAccount(userDetailsBean, accessDetails);
// send updated jwt incase of mobile number update by user
response.addHeader(SecurityConstants.HEADER_STRING,
SecurityConstants.TOKEN_PREFIX + refreshJWT(updatedUserBean.getMobileNumber()));
return buildResponse(updatedUserBean);
}
catch( DataException e )
{
return buildError(e);
}
}
private String refreshJWT( String subject )
{
return Jwts.builder().setSubject((subject))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET).compact();
}
This is working. If anyone has a cleaner and industry standard approach please specify.
If you allow your users to change their usernames, they should also have an immutable user id that can be used to identify any data or activity associated with a given user. Otherwise, any time a user changes his or her name, you will either lose the ability to audit the user's past actions or you will have to update all references to that username in the database. What's worse is if there are references to an old username in the database and another user takes that username -- now you have data from one user now being associated with another due to incorrect handling of user identification.
Now with that said, the sub claim should contain this immutable user id. You can create a separate claim for the mutable username. When a username is changed, you now only need to change a single field in the database (assuming that only the users table references this mutable username). You could then use the refresh token retrieve a new token that would contain the latest username that could then be used by your API as needed.
Using this approach, you should be careful to only use the username claim for display purposes, not for identifying the logged in user due to the fact that it is mutable. The sub claim containing the user id would serve the purpose of identifying a user.
It is also important to note that this solution requires no special logic for "updating the sub claim." You would be using the same logic that you're already using to generate a token for a supplied refresh token.

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

MVC3 - Call logon method from GET request

I need to be able to call the built-in [HttpPost] Logon method (in the Account Controller) from a GET request. Basically, a new account would be assigned a temporary password, delivered via email. The user clicks on the link in the email (containing a userid and temp pw in the URL), which would be handled in the account controller in the MVC app. I'm not sure how to handle this. Is it possible to redirect from a GET action method on the controller, to the POST logon action method? How would this be done while maintaining the HttpContext object? It seems that this wouldn't be doable outside of just creating a new GET logon method with the user and pw strings params and recreating the logic (using MembershipServer and FormsService objects) that's in the POST method.
EDIT: For anyone following this post, the solution I wound up implementing is:
Using #Shyju's recommendation in a comment, I created a new GET action method to respond to the email link, that returns a "Register New User" view containing hidden inputs for the passed user\pw and a submit button.
The view posts back to the normal logon method. If the user or pw has not been altered, the user will be authenticated
I added a global action filter that tests for the user object property "MustChangePassword". If true, it redirects to the change password action method. This will happen for any method of any controller, except the account controller (so you can actually hit the logon, chg pw, and other methods)
Then in the "ChangePassword" POST method, a successful pw change will reset the "MustChangePassword" property to false.
You can create a unique string (use Guid) for each user and send that as the query string for the link in the email. In the GET action validate that against the user table & Temporary Password table and get the user record and consider that user as logged in user.

Resources