djangodjoser

Djoser reset password flow to javascript frontend (Vue)


I've got most of this reset password flow done using djoser in django. I get the email successfully. The last part where I redirect back to Vue.js frontend after the user follows the link in the email is the only thing I'm missing. For clarity I expect to be redirected to my Vue frontent url with uid and token present. From there I can finish this off. My django files are:

backend/settings.py (look at DJOSER setup):

"""
Django settings for backend project.

Generated by 'django-admin startproject' using Django 4.2.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

from pathlib import Path
from django.urls import reverse

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
BASE_URL = 'http://127.0.0.1:8080'

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-1z1=&mfysp%pklgh@x+^_j3+p5)x+zm@va36wc9eq0z=7r3u7z'

STRIPE_SECRET_KEY = 'sk_test_51NH4LnIIOIhiNEiGlcpsukLE5pi6Me9zYOhTf1uVP4ux7WgCXyR5QG9b6Yzfgm6qB4Agl8ZcQwZ7RN2rKIex04nk00kF97Aeok'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['127.0.0.1']

CORS_ALLOW_CREDENTIALS = True

CSRF_TRUSTED_ORIGINS = [
    'http://127.0.0.1:8080',
]

CORS_ALLOWED_ORIGINS = [
    'http://127.0.0.1:8080',
]

CSRF_COOKIE_HTTPONLY = False

# SESSION_COOKIE_SAMESITE = 'None'
# CORS_ALLOW_CREDENTIALS = True

EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / 'emails'



# Application definition
DJOSER = {
    'PASSWORD_RESET_CONFIRM_URL': 'api/v1/password/reset/confirm/{uid}/{token}', #help me change this please
    
    # 'ACTIVATION_URL': '#/api/v1/activate/{uid}/{token}',
    'SEND_ACTIVATION_EMAIL': True,
    'SERIALIZERS': {},
}

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
    'djoser',

    'product',
    'order',
    'email_app'
]


MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'backend.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'backend.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = 'static/'
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

backend/urls.py:

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('djoser.urls')),
    path('api/v1/', include('djoser.urls.authtoken')),
    path('api/v1/', include('product.urls')),
    path('api/v1/', include('order.urls')),
    path('api/v1/', include('email_app.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

in the frontend I have routes for ResetPassword, ResetPasswordDone and ResetPasswordConfirm.

frontend/router/index.js:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

import store from '../store'


import ResetPassword from '../views/ResetPassword.vue'
import ResetPasswordConfirm from '../views/ResetPasswordConfirm.vue'
import ResetPasswordDone from '../views/ResetPasswordDone.vue'

const routes = [
  
  
  {
    path: '/reset-password',
    name: 'ResetPassword',
    component: ResetPassword
  },
  {
    path: '/reset-password-done',
    name: 'ResetPasswordDone',
    component: ResetPasswordDone
  },
  {
    path: '/api/v1/password/reset/confirm/:{uid}/:{token}', // this is the path I want DJOSER to redirect to with `uid` and `token`
    name: 'ResetPasswordConfirm',
    component: ResetPasswordConfirm
  },
]

Steps I take regarding DJOSER:

  1. post to api/v1/reset_password
                const config = {
                    headers:{
                        'Content-Type': 'application/json'
                    }
                }
                const email = this.email;
                const body = JSON.stringify({ email })
                
                axios.defaults.withCredentials = true;
                axios.defaults.headers.common = {
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-CSRFToken' : this.getCookie('csrftoken')
                };


                axios.post('/api/v1/users/reset_password/', body, config)
  1. djoser creates email with this link,

backend/emails:

Content-Type: multipart/alternative;
 boundary="===============0720596674918646286=="
MIME-Version: 1.0
Subject: Password reset on 127.0.0.1:8000
From: webmaster@localhost
To: kenneth.shimabukuro@gmail.com
Date: Fri, 21 Jul 2023 11:01:05 -0000
Message-ID: <168993726590.34747.17316762060486301205@2.1.168.192.in-addr.arpa>

--===============0720596674918646286==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

You're receiving this email because you requested a password reset for your user account at 127.0.0.1:8000.

Please go to the following page and choose a new password:
http://127.0.0.1:8000/api/v1/password/reset/confirm/NQ/brop9t-929c0b1e6031f0d34ca241f1867ba0c1
Your username, in case you've forgotten: ken4

Thanks for using our site!

The 127.0.0.1:8000 team

All I need is djoser to send an email redirecting to http:127.0.0.1:8080 not http:127.0.0.1:8000 with the uid and token present. How do I do that?


Solution

  • Feel like I'm talking to myself here but here's the answer (perhaps I asked badly). Add DOMAIN in settings.py. This is based on the django docs and this answer that specify if your backend and frontend are on different ports then DOMAIN needs to be specified

    Updated settings.py:

    ...
    DOMAIN = '127.0.0.1:8080'
    SITE_NAME = 'd-commerce'
    ...
    
    DJOSER = {
        'PASSWORD_RESET_CONFIRM_URL': 'api/v1/users/reset_password_confirm/{uid}/{token}',
        'PASSWORD_RESET_CONFIRM_RETYPE' : True,
        'SERIALIZERS': {},
    }
    ...
    

    From there you'll receive an email

    Content-Type: multipart/alternative;
     boundary="===============5014392667940436431=="
    MIME-Version: 1.0
    Subject: Password reset on d-commerce
    From: webmaster@localhost
    To: a@a.com
    Date: Mon, 24 Jul 2023 13:33:30 -0000
    Message-ID: <169020561045.43044.9860348640042926888@2.1.168.192.in-addr.arpa>
    
    --===============5014392667940436431==
    Content-Type: text/plain; charset="utf-8"
    MIME-Version: 1.0
    Content-Transfer-Encoding: 7bit
    
    You're receiving this email because you requested a password reset for your user account at d-commerce.
    
    Please go to the following page and choose a new password:
    http://127.0.0.1:8080/api/v1/users/reset_password_confirm/MTI/brugbu-c956f64f925f359d044a85c405aff712
    Your username, in case you've forgotten: t1
    
    Thanks for using our site!
    
    The d-commerce team
    --===============5014392667940436431==
    Content-Type: text/html; charset="utf-8"
    MIME-Version: 1.0
    Content-Transfer-Encoding: 7bit
    

    The frontend functionality was easy to figure out from there. Hope this helps somebody else.