İçeriğe geç

Vue.js ve Django Rest Framework ile JWT Authentication

Herkese merhabalar. Bu yazıda ön tarafta Vuex ve Vue-Router ile birlikte Vue.js, arkada ise Django Rest Framework ile geliştirilmekte olan bir projede JWT(Json Web Tokens) ile nasıl kullanıcı doğrulaması yapabileceğimizden bahsedeceğim.

Talk is cheap show me code

Yapacaklarımızı sıralamak gerekirse:

  • Genel yapı hakkında konuşacağız.
  • Backend uygulamasını ayağa kaldıracak ve bağımlılıkları konfigüre edeceğiz.
  • Gerekli API endpointlerini oluşturacağız.
  • Frontend uygulamasını ayağa kaldıracağız.
  • State ve routing ayarlamalarını yapacağız.
  • Bootstrap dahil ederek projeyi biraz daha güzel hale getireceğiz.

Genel yapı.

Web uygulamalarının ayrılmaz parçalarından biri kullanıcı doğrulamasıdır. Kullanıcı doğrulaması ile istekte bulunan kullancıyı kontrol ederek davetsiz misafirlere karşı ilk korumayı sağlamış oluruz.

Django veya benzer full stack bir web framework ile çalışırken bu işlem genellikle framework tarafından yürütülür. Fakat birden fazla istemciye(web,mobil,desktop) hizmet verecek yapılarda ortak bir taban kullanarak doğrulama gerekmektedir.  HTTP protokolü kendi içinde bir doğrulama mekanizmasına sahiptir. Bu mekanizma gelen isteğe ilk olarak doğrulama şemasını döndürür. Daha sonra istemci şemanın gerektirdiği şekilde isteğini düzenler ve tekrar gönderir. En çok kullanılan yöntem ise token bazlı doğrulamadır. Client yani istemci sadece kullancısına ait olan ve içinde diğer kullanıcılardan ayırt edilebilmesini sağlayacak bir takım bilgiler içeren bir tokene sahiptir. Bu token istemci tarafında tutulur. HTTP isteklerinde backend uygulamasına Authorization headerı altında gönderilip, kontrol edilir. Geçerli bir token ise doğrulama sağlanmış olur. Geçerli değil ise istek reddedilir ve yeni bir token elde edebilmesi adına kullanıcı yönlendirilir.

JWT ise yine bu adımları uygularken kullanılabilecek bir yaklaşımdır. Daha detaylı ve teknik bilgiye erişmek isterseniz.


Genel proje dosya yapısı.

tree -I "__pycache*|node_modules"
.
├── db.sqlite3
├── frontend
│   ├── babel.config.js
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   │   ├── favicon.ico
│   │   └── index.html
│   ├── README.md
│   └── src
│       ├── App.vue
│       ├── client.js
│       ├── components
│       │   └── Navigation.vue
│       ├── main.js
│       ├── router
│       │   └── index.js
│       ├── services
│       │   └── user-service.js
│       ├── store
│       │   └── index.js
│       └── views
│           ├── Login.vue
│           ├── Profile.vue
│           └── Register.vue
├── manage.py
├── requirements.txt
└── server
    ├── api
    │   ├── serializers.py
    │   └── views.py
    ├── asgi.py
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

10 directories, 26 files

 

Demo görüntüleri ve uygulanacaklar.

Demo uygulamamız 3 ayrı sayfadan oluşacak. Sırası ile ile kullanıcı kayıt sayfası, giriş sayfası, ve birtakım kullanıcı bilgisinin gösterildiği profil sayfası.

Kayıt sayfası.

demo app register page


Kullanıcı giriş sayfası.demo app login page


Kullanıcı profil sayfası.demo app user profile page


Django uygulamasının ayağa kaldırılması.

Bir sanal ortam oluşturduktan sonra gerekli bağımlılıkları yükleyelim ve kayıt altına alalım.

pip install django djangorestframework django-cors-headers && pip freeze> requirements.txt
django-admin startproject server .

Uygulamamız hazır. Fakat DRF ve CORS uygulamalarını kayıt etmemiz ve ufak konfigürasyonlarını yapmamız gerekmekte.

INSTALLED_APPS = [
    #default django apps
    "rest_framework",
    "corsheaders",
]

Django cors headers esasen bir middleware. Tarayıcıların kullancısını korumak üzere kullandığı sınırlamaları backend ve frontend uygulamalarımızda esnetmek için bulunmakta. Daha detaylı CORS bilgisi için

MIDDLEWARE = [
    #other middlewares
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    #other middlewares
]

Cors middlewarei projenizin gerekliliklerine göre değişmekle birlikte, CommonMiddleware gibi yanıt oluşturabilen middleware lerden önce ve olabildiğince ilk sıralarda olmalı.

Bunun yanında hangi originlerden gelen istekleri kabul edeceğimizi de belirtmemiz gerekmekte. Şu an oluşturmadık fakat standart bir Vue.js uygulaması 8080 portundan yayın yapmakta. Bu istekleri kabul edeceğimizi belirtelim.

CORS_ALLOWED_ORIGINS = [
    "http://localhost:8080",
]

Tamam gözüküyor. Hızlıca proje dizininde bir api klasörü oluşturalım. (Demo bir uygulama olduğu için herhangi bir django app oluşturmayacağız.)

$ tree -I __pycache__ api/
api
├── serializers.py
└── views.py

0 directories, 2 files

serializers.py

from rest_framework import serializers
import random

from django.contrib.auth import get_user_model

User = get_user_model()


class UserSerializer(serializers.ModelSerializer):
    group = serializers.SerializerMethodField()
    password2 = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ["username", "email", "group", "password", "password2"]
        extra_kwargs = {
            "password": {"write_only": True, "style": {"input_type": "password"}},
            "password2": {"style": {"input_type": "password"}},
        }
        read_only_fields = ["group"]

    def get_group(self, obj):
        groups = ["Developer", "Maintainer", "Product owner", "Designer"]
        return random.choice(groups)

    def create(self, validated_data):
        validated_data.pop("password2")
        return User.objects.create_user(**validated_data)

views.py

from rest_framework import generics, permissions
from django.contrib.auth import get_user_model
from .serializers import UserSerializer
from rest_framework_simplejwt import authentication

User = get_user_model()


class UserCreate(generics.CreateAPIView):
    serializer_class = UserSerializer
    queryset = User.objects.all()


class UserDetail(generics.RetrieveAPIView):
    serializer_class = UserSerializer
    queryset = User.objects.all()
    permission_classes = (permissions.IsAuthenticated,)
    authentication_classes = (authentication.JWTAuthentication,)

    def get_object(self):
        return self.request.user

Yukarıda yaptıklarımızı kısaca özetleyecek olursak User modelini baz alan bir model serializer oluşturduk. User modelinde bulunmayan bir kaç alanı da serializere ekledikten sonra create metodu çağrılmadan önce bir validasyona tabi tuttuk. Ayrıca bize fake bir data sağlaması adına serializer method field ile random bir grup ismi geri döndük.

views.py da ise 2 adet endpoint tanımladık. İlki kullanıcı kaydı için kullanılacak.

UserDetail ise JWT ile korunan kullanıcının birtakım bilgilerini serverdan talep ederken kullanacağı endpoint. Gelen doğrulanmış kullancıyı UserSerializer’e göre serialize edip geri dönmekte.

Not1: Model serializerler varsayılan olarak save metodları çağrıldığı zaman, create metodunu yürütürler. create metodu ise Meta classta tanımlanmış olan modelin default managerinde bulunan create  metodunu validasyondan geçirilen parametreler ile tetikleme eğilimindedirler.

Not2: Eğer model serializer de modelde direkt olarak bulunmayan fakat başka işlemler için bulunan alanlar var ise araya girip validated_data dan çıkartmanız gerekmekte. Aksi halde Django gelen verinin model tarafında karşılığı olmadığından dolayı hata fırlatacaktır.

Not3: Not1 de belirtildiği üzere User modelinin create metodu size kayıt edilmiş fakat hashlenmemiş bir parolaya sahip kullanıcı geri döndürür. Bundan dolayı direkt olarak model managerde tanımlı create_user metodunu çağırmamız gerekmektedir.


Son olarak ana proje dosyasındaki root urls.py dosyasına endpointlerimizi kaydetmemiz gerekmekte.

from django.contrib import admin
from django.urls import path
from rest_framework_simplejwt import views
from server.api.views import UserCreate, UserDetail

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/auth/register/", UserCreate.as_view(), name="register"),
    path("api/auth/me/", UserDetail.as_view(), name="me"),
    path(
        "api/auth/obtain/token/",
        views.TokenObtainPairView.as_view(),
        name="obtain-token",
    ),
    path(
        "api/auth/obtain/refreh/",
        views.TokenRefreshView.as_view(),
        name="refresh-token",
    ),
]

Son 2 endpoint bize simple-jwt paketi tarafından sağlanmakta. İlki sistemden bir token isteği yapmak için kullanılmakta. İstemci kullanıcı adı ve parolasından oluşan bir payload ile sistemden token elde edebilir. Bunun sonucunda access ve refresh olmak üzere 2 token dönülmekte. Son endpointte bununla ilgili. Eğer izin vermiş isek kullanıcı servere refresh tokenını gönderip yeni bir token talep edebilir. Django Rest Framework ve JWT authentication ile daha fazla bilgi almak isterseniz.


Vue.js uygulamasının ayağa kaldırılması.

Node.js, npm, vue-cli yüklü olduğu varsayılmıştır.

Proje dizininde vue cli yardımı ile varsayılan özelliklere sahip bir proje oluşturalım.

npm install --save vuex vue-router bootstrap jquery popper.js axios

HelloWorld ve diğer componentleri temizleyelim.


Planlama.

Çalışmaya başlamadan önce genel hatlar ile düşünecek olursak kullanıcının backend ile etkileşime geçeceği 3 adet endpoint bulunmakta. Bu yüzden bir HTTP client oluşturmamız gerekmekte. Bu clienti kullanan bir servis yazmak uygulamamızı daha yönetilebilir hale getirecektir.

Daha sonra uygulama genelinde kullanılacak olan işlemleri veya veriyi tutması için bir central state management oluşturmamız gerekmekte. Böylece componentler arası iletişimlerde veya veri doğruluğu problemleri yaşamayıp tek bir yerden tüm uygulama datasını ve asenkron işlemleri yöneteceğiz. Teşekkürler Vuex 🙂

Birden fazla sayfası olan bir uygulama geliştirdiğimiz için bize bir ader router lazım olacak. Bu router gelen isteğin urline göre doğru componenti yükleyecek. Bunun yanında routelara guardlar ekleyerek erişimi kısıtlı hale getireceğiz.

Daha sonrasında componentlerimizi yazmamız ve uygulamamızı test etmemiz gerekmekte.

Genel hatlar ile yapılacak işlemler bunlar fakat adımlar içerisinde konuşacağımız ufak trickler yine olacak.

Öneri: Import error gibi hatalar almak istemiyorsanız en başta paylaşılan yapıyı dizininizde oluşturmanız yararınıza olacaktır.


Client ve servislerin oluşturulması.

.env.local

VUE_APP_BACKEND_API_URL=http://localhost:8000/api/

client.js

import axios from "axios";
import router from "./router";
import store from "./store";

const client = axios.create({
  baseURL: process.env.BACKEND_API_URL || "http://localhost:8000/api/",
  headers: {
    "Content-Type": "application/json",
  },
});

client.interceptors.request.use(
  (config) => {
    let token = localStorage.getItem("accessToken") || "";
    config.headers["Authorization"] = `Bearer ${token}`;
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

client.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    if (error.response.status == 401) {
      store.commit("logout");
      if (router.currentRoute.name === "login") {
        return Promise.reject(error);
      }
      router.push({ name: "login" });
    }
    return Promise.reject(error);
  }
);

export default client;

Sırası ile axios instance’ı yaratıyoruz. .env dosyasından backend root api okuyor base url ayarlıyoruz. Daha sonrasında kullanılacak olan content typeı geçiyoruz.

Token ile doğrulama yöntemlerinde tokenın istemci tarafında tutulduğundan bahsetmiştik. Web uygulamalarında bu görevi tarayıcıların localStorage’ı üstlenmekte. Buradan elde edilen token isteğin Authorization headerına eklenmekte ve istek o şekilde gönderilmekte.

Bu yönetimi sağlama adına axios ta interceptorlar bulunmakta. Global olarak o axios istemcisi üzerinden yapılan istekleri modifikasyona uğratabiliyoruz.

Aynı şekilde dönen cevapların sonuçlarına göre de bir takım işlemler yapabiliyoruz.

Eğer 401 yani kullanıcı doğrulama başarısız olmuş ise kullanıcıyı yeni bir token elde etmesi adına giriş sayfasına yönlendiriyoruz.

login sayfasında da bu hatayı alabileceğinden ötürü ufak bir kontrol yaparak, kullanıcının tekrar aynı sayfaya yönlendirilmesinin önüne geçiyoruz.

Not: Uygulama genelinde farklı doğrulama yöntemleri kullanacak iseniz interceptor’u parametrik hale getirebilirsiniz.

services/user-service.js

import client from "../client";

class UserService {
  login(payload) {
    return client.post("auth/obtain/token/", payload);
  }
  register(payload) {
    return client.post("auth/register/", payload);
  }
  userDetail() {
    return client.get("auth/me/");
  }
}

export default new UserService();

Servis payload ları alıp isteği yaptıktan sonra promise olarak bize geri döndürüyor.


Vuex yapısının oluşturulması.

import Vue from "vue";
import Vuex from "vuex";
import userService from "../services/user-service";

Vue.use(Vuex);

const initialUser = () => {
  let token = localStorage.getItem("accessToken");
  if (token)
    return { loggedIn: true, username: "", role: "", accessToken: token };
  else return { loggedIn: false, username: "", role: "", accessToken: null };
};

export default new Vuex.Store({
  state: {
    user: initialUser(),
  },
  getters: {
    isLoggedIn: (state) => state.user.loggedIn,
    userData: (state) => state.user,
  },
  mutations: {
    loginSuccessfull(state, token) {
      state.user.loggedIn = true;
      state.user.accessToken = token;
      localStorage.setItem("accessToken", token);
    },
    logout(state) {
      (state.user.loggedIn = false), localStorage.removeItem("accessToken");
    },
    setUserData(state, userInfo) {
      (state.user.role = userInfo.group),
        (state.user.username = userInfo.username);
    },
  },
  actions: {
    loginRequest(context, credentials) {
      return userService
        .login(credentials)
        .then((res) => {
          context.commit("loginSuccessfull", res.data.access);
          return Promise.resolve(res.data);
        })
        .catch((err) => {
          return Promise.reject(err);
        });
    },
    registerRequest(context, payload) {
      return userService
        .register(payload)
        .then((res) => {
          return Promise.resolve(res.data);
        })
        .catch((err) => {
          return Promise.reject(err);
        });
    },
    getUserData(context) {
      return userService
        .userDetail()
        .then((res) => {
          context.commit("setUserData", res.data);
          return Promise.resolve(res.data);
        })
        .catch((err) => {
          return Promise.reject(err);
        });
    },
    logoutRequest(context) {
      return new Promise((resolve) => {
        context.commit("logout");
        resolve();
      });
    },
  },
});

Modern web uygulamalarında state yönetimi fazlası ile önemlidir. En basit tabir ile uygulamanızın merkezinde bulunan store, uygulamanızın herhangi bir yerindeki bir component veya herhangi bir dış servis ile iletişime geçebilir. Tamami ile reaktiftir. Değişen datayı kullanılan her yerde değiştirmek zorunda kalmazsınız. Ayrıca gelecek olan datanın doğruluğundan kesin olarak emin olursunuz.

Kendi içinde bir tarihe sahip olduğu için değişimleri izleyebilir ve bağlı olan değişimleri de çağırabilirsiniz.

Kısaca 5 unsurdan oluşur.

  • State (Veriyi saklar)
  • Mutations(Veriyi değiştirirken kullanılır. Senkron çalışır. State’i doğrudan değiştirir.)
  • Actions(Genellikle dış servisler ile çalışırken kullanılır. Asenkron çalışır. Gelen cevaba göre state’i güncellemek üzere bir mutation commit eder.)
  • Getters(Uygulamada hali hazırda bulunan state verisinin bir kısmını veya tamamını geri döndürebilir.)
  • Modules (Büyük uygulamalarda yönetimi kolaylaştırm adına kullanılır.)

Kısaca özetleyecek olursak genellikle componentler store da bulunan bir aksiyonu dispatch eder, aksiyonun işlemi yaptıktan sonra başarı durumuna göre bir mutation commitler. Mutation state içinde bulunan datayı değiştirir. Component içinde state datasını kullanan her kısım genellikle getters lar ile çağrılır. State değiştiği anda reaktif olarak gettersların döndürdüğü değerler değişir ve uygulama güncellenmiş olur. Daha fazla ve teknik bilgi için Vuex.

State i hesaplar iken bir fonksiyon kullanabiliriz. Tokenlerı istemcide tuttuğumuzdan bahsetmiştik. Burada bir token bulur ise kullancıyı giriş yapmış sayıyoruz. Böylece token geçerlilik süresini yitirene kadar sistem dışına çıkarılmamış olacak. Aksi durumda token değiştirilmiş ve geçerlilik süresi dolmuş ise 401 hatasını yöneten interceptor bir mutation çağıracak localStorage’ı temizleyecek ve bizi tekrar bir token isteği yapabilmemiz için giriş sayfasına yönlendirecektir.

İsteğin başarılı veya başarısız olma durumuna göre ilgili componentte hata yönetimi veya yönlendirme yapmak isteyebiliriz. Bunun için yine geriye reddedilmiş veya çözülmüş bir promise döndürüyoruz.


Router konfirügasyonu.

router/index.js

import VueRouter from "vue-router";
import Vue from "vue";
import Profile from "@/views/Profile";
import Login from "@/views/Login";
import Register from "@/views/Register";
import store from "../store";

Vue.use(VueRouter);
const routes = [
  {
    path: "",
    component: Profile,
    name: "profile",
    meta: {
      requiresAuth: true,
    },
  },
  {
    path: "/login",
    component: Login,
    name: "login",
  },
  {
    path: "/register",
    component: Register,
    name: "register",
  },
];

const router = new VueRouter({
  routes,
  mode: "history",
});

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    if (!store.state.user.loggedIn) next({ name: "login" });
    else next();
  }
  next();
});

export default router;

Vue-Router bizim için gelecek isteğe göre ilgili komponenti yüklemekle görevli. routes adlı array içinde route objelerini barındırmakta. Tahmin edeceğiniz üzere path gelen istek urlini, component yüklenecek olan viewı, name ise yönlendirme sırasında kullanmak üzere named route oluşturmak için kullanılıyor.

Daha sonra bir router nesnesi oluşturuyor ve yukarıdaki arrayi routes olarak içeri geçiyoruz.

beforeEach metodu yardımı ile mevcut route değişirken bir takım işlemler yapabiliriz. Kullanıcı yetki kontrolü, giriş yapıp yapmadığı vs. Bu arada 3 ana parametre zorunlu.

to gideceğimiz route u temsil ediyor.

from ayrıldığımız route.

next ise isteğin devam etmesi veya yönlendirilmesi için kullanılmakta.

Bu örnekte meta özelliğinde requiresAuth tanımlanan her route için kullanıcı doğrulaması yapılacak yapılamadığı durumlarda yönlendirme sağlanacak. Fakat burada next i çağırır iken yapıyı iyi kurgulamamız gerekmekte aksi halde recursion error almanız olası. Kurguya göre değişmekle birlikte, sistemin çalışmaya devam etmesi için next çağırılmalı.


Store ve router ayarlamlarımızı yaptığımıza göre componetlerimizi ve viewlarımızı oluşturabiliriz.

Öncesinde ana Vue nesnemize bir store ve router kullanacağımızı belirtelim. Biraz güzel gözükmesi adına Bootstrap’i de içeriye aktaralım.

Not: Kullanacağımız herhangi bir plugin var ise bunu ana Vue nesnemize dahil etmemiz gerekmekte.

import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import router from "./router";
Vue.config.productionTip = false;
import "bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";

new Vue({
  render: (h) => h(App),
  store,
  router,
}).$mount("#app");

Componentler ve viewlar.

app.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
};
</script>

<style></style>

router-view VueRouter tarafından sağlanan özel bir keyword.

Ana App.vue dosyamıza burada render edilecek olan viewların router tarafından yönetileceğini belirtiyoruz.


components/Navigation.vue

<template>
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <span class="navbar-brand mb-0 h1">dj-vue-jwt</span>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav ml-auto">
        <template v-if="!isLoggedIn">
          <li class="nav-item">
            <router-link
              active-class="active"
              tag="a"
              class="nav-link"
              :to="{ name: 'login' }"
              >Login</router-link
            >
          </li>
          <li class="nav-item">
            <router-link
              active-class="active"
              tag="a"
              class="nav-link"
              :to="{ name: 'register' }"
              >Register</router-link
            >
          </li></template
        >

        <li v-else class="nav-item">
          <a @click="logout()" class="nav-link">Logout</a>
        </li>
      </ul>
    </div>
  </nav></template
>

<script>
import { mapGetters, mapActions } from "vuex";
export default {
  computed: {
    ...mapGetters(["isLoggedIn"]),
  },
  methods: {
    ...mapActions(["logoutRequest"]),
    logout() {
      this.logoutRequest().then(() => {
        this.$router.push({ name: "login" });
      });
    },
  },
};
</script>

<style></style>

İlk componentimiz Navigation.vue. Burada yine router tarafından sağlanan router-link ile dinamik olarak route u değiştiriyoruz. to parametresi tıklanıldığında gidilecek olan route named route views kullanarak çağırmakta. Vuex helperları mapGetters,mapActions yardımı ile Vuex store umuzun yeteneklerini componente kazandırıyoruz. Ve sanki o componentin bir metodu veya datasıymış gibi kullanmamıza olanak veriyor.

State kullanıcı durumuna göre gösterilecek olan linkleri dinamik olarak kontrol ediyor, logout metodu yardımı ile bir action dispatch edip sonucuna göre yönlendirme yapıyoruz.


views/Register.vue

<template>
  <div class="container">
    <div class="row">
      <div class="col">
        <navigation></navigation>
      </div>
    </div>
    <div v-if="error.status" class="row mt-5">
      <div class="col d-flex justify-content-center">
        <div class="alert alert-danger">
          {{ error.message }}
        </div>
      </div>
    </div>
    <div class="row mt-5">
      <div class="col d-flex justify-content-center">
        <form @submit.prevent="register()">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              type="text"
              class="form-control"
              id="username"
              aria-describedby="emailHelp"
              placeholder="Enter username"
              v-model="credentials.username"
            />
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              class="form-control"
              id="password"
              placeholder="Password"
              v-model="credentials.password"
              required
            />
          </div>
          <div class="form-group">
            <label for="password2">Password repeat</label>
            <input
              type="password"
              class="form-control"
              id="password2"
              placeholder="Password repeat"
              v-model="credentials.password2"
              required
            />
          </div>
          <button
            type="submit"
            class="btn btn-primary btn-block"
            :disabled="!passwordEquality"
          >
            Register
          </button>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
import Navigation from "@/components/Navigation";
export default {
  components: {
    Navigation,
  },
  data() {
    return {
      credentials: {
        username: "",
        email: "",
        password: "",
        password2: "",
      },
      error: {
        status: false,
        message: "",
      },
    };
  },
  methods: {
    async register() {
      let payload = { ...this.credentials };
      try {
        await this.$store.dispatch("registerRequest", payload);
        this.$router.push({ name: "login" });
      } catch (error) {
        this.error.status = true;
        this.error.message =
          error.response.data.username[0] ||
          error.response.detail ||
          "Something went wrong!";
      }
    },
  },
  computed: {
    passwordEquality() {
      if (this.credentials.password === this.credentials.password2) return true;
      else return false;
    },
  },
};
</script>

<style></style>

views/Login.vue

<template>
  <div class="container">
    <div class="row">
      <div class="col">
        <navigation></navigation>
      </div>
    </div>
    <div v-if="error.status" class="row mt-5">
      <div class="col d-flex justify-content-center">
        <div class="alert alert-danger">
          {{ error.message }}
        </div>
      </div>
    </div>
    <div class="row mt-5">
      <div class="col d-flex justify-content-center">
        <form @submit.prevent="login()">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              type="text"
              class="form-control"
              id="username"
              aria-describedby="emailHelp"
              placeholder="Enter username"
              v-model="credentials.username"
            />
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              class="form-control"
              id="password"
              placeholder="Password"
              v-model="credentials.password"
            />
          </div>

          <button
           
            type="submit"
            class="btn btn-primary btn-block"
          >
            Login
          </button>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
import Navigation from "@/components/Navigation";
export default {
  components: {
    Navigation,
  },
  data() {
    return {
      error: {
        status: false,
        message: "",
      },
      credentials: {
        username: "",
        password: "",
      },
    };
  },
  methods: {
    login() {
      let payload = { ...this.credentials };
      this.$store
        .dispatch("loginRequest", payload)
        .then(() => {
          this.$router.push({ name: "profile" });
        })
        .catch((error) => {
          (this.error.status = true),
            (this.error.message =
              error.response.data ||
              error.response.error ||
              error.response.detail ||
              "Unable to login with given credentials.");
        });
    },
  },
};
</script>

<style></style>

views/Profile.vue

<template>
  <div class="container">
    <div class="row">
      <div class="col">
        <navigation></navigation>
      </div>
    </div>
    <div class="row mt-5">
      <div class="col">
        <div class="jumbotron jumbotron">
          <div class="container">
            <h1 class="display-5">Hi {{ userData.username }}!</h1>
            <p class="lead mt-5">Your system group > {{ userData.role }}</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Navigation from "@/components/Navigation";
import { mapActions, mapGetters } from "vuex";
export default {
  components: {
    Navigation,
  },
  methods: {
    ...mapActions(["getUserData"]),
  },
  created() {
    this.getUserData().catch(() => {});
  },
  computed: {
    ...mapGetters(["userData"]),
  },
};
</script>

<style></style>

Uygulamamızı componentler ile küçük parçalara böldükten sonra render edilecek kısımlarda birleştirerek kompleks yapılar oluşturabiliriz. Navigation componenti bütün viewlarımızın bir parçası ve hepsinde içeriye aktarılmış ve kayıt edilmiş durumda. Direkt olarak native bir HTML elementiymiş gibi kullanılabilir.

Yine benzer bir şekilde Vuex helperları ile componente bir takım yetenekler kazandırıyoruz. Bir action dispatch ediyor ve sonucuna göre hata gösterimi veya yönlendirme yapıyoruz. Ayrıca computed propertyler ile dinamik olarak bir hesaplama yapıyor, sonucua göre attribute binding işlemini gerçekleştiriyoruz.


Fazlası ile uzun bir yazı oldu. Ama bir kaç işlem daha var 🙂 Uygulamayı görmek!

./manage.py runserver 8000
cd yourFrontendAppName/ && npm run serve

Backend ve frontendi ayağa kaldırdıktan sonra uygulamanızı localhost:8080 portundan görebilirsiniz. Django’ya erişmek isterseniz gitmeniz gereken yeri biliyorsunuz 🙂 8000


Son sözler.

Öncelikle tebrikler!

Bugün hatırı sayılır miktarda bilgi öğrendik. Umuyorum ki Django, Django Rest Framework ve Vue.js ile modern bir web uygulaması geliştirirken JWT authentication uygulanması sırasında kabaca hangi adımları izlemeniz gerektiğini ve 2 frameworkünde genel çalışma mantığını anladınız.

Vue ve Django ile uygulama geliştirmek fazlası ile kolay ve keyifli. Biraz ara verip en sevdiğiniz kahveyi demleyip bir fincan koyup demo uygulamayı tekrar kodlamak size fazlası ile fayda sağlayacaktır.

Eğer bir yerlerde takılır iseniz uygulamanın kodları şurada yaşıyor.

Bu yazıyı yararlı buldu iseniz paylaşabilir ve daha fazla insana ulaştırabilirsiniz. Bir sonraki yazı da görüşmek üzere!

 

 

 

Tarih:Blog

Tek Yorum

  1. ömer ömer

    emeğine sağlık kardeşim

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle