DEV Community

Cover image for Next.js & Django JWT Authentication: Django REST, TypeScript, JWT, Wretch & Djoser

Next.js & Django JWT Authentication: Django REST, TypeScript, JWT, Wretch & Djoser

Mangabo Kolawole on February 12, 2024

If you're a software engineer, especially a full-stack developer, your job involves merging different technologies—like backend and frontend—to bui...
Collapse
 
cd3v profile image
cd3v • Edited

Ideally when setting the cookie, the django backend dictates this by doing something such as below when request is made to the login api endpoint

``
class CustomTokenObtainPairView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)

    if response.status_code == 200:
        access_token = response.data.get('access')
        refresh_token = response.data.get('refresh')

        response.set_cookie(
            'access',
            access_token,
            max_age=settings.AUTH_COOKIE_ACCESS_MAX_AGE,
            path=settings.AUTH_COOKIE_PATH,
            secure=settings.AUTH_COOKIE_SECURE,
            httponly=settings.AUTH_COOKIE_HTTP_ONLY,
            samesite=settings.AUTH_COOKIE_SAMESITE
        )
        response.set_cookie(
            'refresh',
            refresh_token,
            max_age=settings.AUTH_COOKIE_REFRESH_MAX_AGE,
            path=settings.AUTH_COOKIE_PATH,
            secure=settings.AUTH_COOKIE_SECURE,
            httponly=settings.AUTH_COOKIE_HTTP_ONLY,
            samesite=settings.AUTH_COOKIE_SAMESITE
        )

    return response
Enter fullscreen mode Exit fullscreen mode

``

With your approach, I haven't been able to use the cookies being set from the django backend on my frontend. Also, is there anyway to set the httponly flag using your approach? All attempts at that have failed since you are setting the cookies through js on the client itself.

Collapse
 
happycoder77 profile image
Saul Rojas Garcia

Hi, thank you for this tutorial.So far so good but when I try to login I receive the error "username field is required" as I can see the username is not in the login form. I have no finished the tutorial yet but I don't know if I am doing it well

Collapse
 
koladev profile image
Mangabo Kolawole

Can I see an example of payload you are using?

Collapse
 
pszentg profile image
pszentg

Hi!

Wonderful post, simple, easy to follow, detailed enough so I have a clear understanding how all these things fit together.

I was wondering how would you approach validating the user object that you get from SWR? I was hoping to get the user object before you render the dashboard. The reason is that I want to provide an admin UI (not the one provided by django!) and I want to route the user eg. based on the "is_staff" flag. I tried something like this in Login.tsx:

const onSubmit = (data: FormData) => {
    login(data.email, data.password)
      .json((json) => {
        storeToken(json.access, 'access');
        storeToken(json.refresh, 'refresh');
      })
      .catch((err) => {
        setError('root', { type: 'manual', message: err.json.detail });
      });

    const { data: user, isLoading, isValidating } = useSWR('/auth/users/me', fetcher);
    // store the user here in a context manager?

    router.push('dashboard');
  };
Enter fullscreen mode Exit fullscreen mode

but it seems I can't use the SWR hook here:

Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

Also, how would you store the user object? Should you even at all, or should I just use this hook in every component I need it? I want to avoid prop drilling but I also want to avoid calling swr in every component.

Collapse
 
koladev profile image
Mangabo Kolawole

An idea might be to put the onSubmit method inside a hook function and call it from there.

Something like

const useAuth = () => {

   const onLoginSubmit = () => {login logic there}

   return {onLoginSubmit}
}
Enter fullscreen mode Exit fullscreen mode

Another idea might be to rewrite the views.TokenObtainPairView from the rest_framework_simplejwt package, so you can add the user object when the login is done.

For storing the user object, if it doesn't change like that, you can use the SWR cache revalidation and deactivate it.

Then calling const { data: user, isLoading, isValidating } = useSWR('/auth/users/me', fetcher); won't call the API every time. But you will need to handle the data revalidation.

You can also check with the network package you are using. In this case, we are using wretch, so you can read more about their caching middleware. elbywan.github.io/wretch/api/modul...

Collapse
 
ngominhthoai profile image
thoaingo

Hi, thank you for the post. However, I've encountered an issue with your logic code. When you define a "Login" component in the frontend that uses email for authentication, the backend (Django or Djoser) defaults to using username for authentication. This mismatch means that attempting to log in with email and password fails, preventing access to the dashboard.

Collapse
 
koladev profile image
Mangabo Kolawole

That was a big mistake on my part then. I corrected it.

In the DJOSER setting, we should have this line too


DJOSER = {
    ...
    'LOGIN_FIELD': 'email',
}
Enter fullscreen mode Exit fullscreen mode

thank you

Collapse
 
rordrigo profile image
rodrigo

Hello! Thank you for the post. I have been working on a project which have the similar stack. I am currently trying to make the application server rendered application. So, for that it requires to fetch data in the server components. It is possible to fetch the data in the server components by also refreshing the access token and continuing the request in the server component as I am currently doing it in the client component. Is it possible to completely develop a server side next frontend app with external backend API. I have researched a lot about it and cant find the way to fetch the data and refresh the token in the server component. I am using iron-session to store the session but having difficulty in refreshing the access token and retrying the request with new token in the header. Once again thank you for the post, I am currently learning through internet and diifferent blogs. This one helped me a lot.

Collapse
 
koladev profile image
Mangabo Kolawole

Thank you for your comment @rordrigo

The pattern you are describing is possible but you will be passing the context every time to the server to retrieve the tokens.
Regarding the refresh logic, if we follow your proposed pattern, I believe that the refresh request will come from the server and I honestly do not see how you can set tokens from the server side ( I might be wrong.)

I think that it will be better to manage the refreshing on the client side unless I am missing an important implementation detail.

Collapse
 
yoshida_daisuke_6869c822f profile image
Yoshida Daisuke

Thank for your tutorial.
It is very useful for my project build

Collapse
 
koladev profile image
Mangabo Kolawole

happy to know it helped!

Collapse
 
rcmisk profile image
rcmisk

Love it!

Collapse
 
koladev profile image
Mangabo Kolawole

thank you @rcmisk

Collapse
 
serhii56465 profile image
Serhii Pastukhov

Hi! Thank you for your post.
I'm reading it now.
But I'm confused with this npm install wretch swr js-cookies react-hook-form, specifically with js-cookies.
Are you sure that we have to install js-cookies, but not js-cookie?

Collapse
 
koladev profile image
Mangabo Kolawole

Hi! this is definitely a typo. Let me correct it.

thank you!

Collapse
 
allengod profile image
Allen

Hi Kola,

So, I was trying to implement the verify email feature, but I haven't had a luck so far.

My difficulty is in the frontend, I already implemented this at the backend, and it works, but I'm having difficulty implementing this at the frontend.

I would appreciate any help implementing this.
Thank You.

Collapse
 
koladev profile image
Mangabo Kolawole

Hi Allen,

Sure I can help. Can you describe your issue with a comment? or if you need quick answers, you can email me at koladev32[at]gmail.com

Collapse
 
patana_rattananavathong_4 profile image
Patana Rattananavathong

Thank you for an awesome tutorial. There's one typo I found along the way at:
const accessToken = cookieStore.get("accesssToken");
There is an extra 's' there.

Collapse
 
koladev profile image
Mangabo Kolawole

thank you very much. I've corrected it.

Collapse
 
patana_rattananavathong_4 profile image
Patana Rattananavathong • Edited

Nice!
I forgot that there's actually another spot.



const login = (email: string, password: string) => {
  return api.post({ username: email, password }, "/auth/jwt/create");
};


Enter fullscreen mode Exit fullscreen mode

The 'username' should be 'email' instead. Like this.



const login = (email: string, password: string) => {
  return api.post({ email: email, password }, "/auth/jwt/create");
};


Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
koladev profile image
Mangabo Kolawole

Thank you very much. Very strange that I missed that part. I believe I was trying to make the code more simple and was dealing with some semantics and I inadvertently omitted to make some corrections.

I will pay more attention to that. Thanks again. I have updated the code.

Collapse
 
luciano_47d8742db393ab270 profile image
Luciano

Hey Im having a little problem on the register part, the backend just tells me I'm sending a bad request and after looking it says that the username field is missing then "creates the user" but when trying to log in it says no active user with this credentials

Collapse
 
koladev profile image
Mangabo Kolawole

Sorry for the late reply. Have you been able to solve your issue?

Collapse
 
aszabonorbert profile image
Norbert Szabó

Hi,
I set LOGIN_FIELD to 'email' in DJOSER settings, but Django complains about the 'username' yet:
UserManager.create_user() missing 1 required positional argument: 'username'

Any idea?

Collapse
 
ismail212 profile image
Ismail • Edited

Hi, new learner here ! Thank you for this very useful tutorial. Can you please explain where the users data is stored once the user is registered? Isn't on the django backend or nextjs ?

Collapse
 
koladev profile image
Mangabo Kolawole

hi! it is on the Django backend. Next.js makes the HTTP request to the backend using wretch.

Collapse
 
ismail212 profile image
Ismail

Much appreciated ! Thanks