Giriş.
Web uygulamaları artık hayatımızın tam merkezinde yer alıyor ve gün içerisinde neredeyse onlarcasıyla muhatap oluyoruz; alışveriş yapıyor, bankacılık işlemlerimizi yönetiyor veya aktivitelerimizi takip ediyoruz.
Eskiden sadece bir kullanıcı adı ve parola ile giriş yapabildiğimiz bu uygulamalar, hayatımızı kolaylaştırmak adına büyük bir evrim geçirdi. Yoğun kullanımımızla birlikte birçok kişisel ve ticari veriyi barındıran bu uygulamalar, bilginin çok değerli olduğu bu çağda siber tehditlere karşı çok daha göz önünde bir hale geldi.
Bu nedenle, uygulamalar güvenlik standartlarını yükseltmeye çalışıyor, basit doğrulama yöntemlerini terk edip kullanıcılarını birden fazla adımda doğrulayarak güvenliği artırmayı hedefliyor.
Bugün sizlerinde mutlaka daha önce karşılaşmış olduğunuz iki aşamalı kimlik doğrulama konusuna önce teorik göz atacak, ardından en yalın haliyle bu yaklaşımı bir Django projesine nasıl entegre edebileceğimizi örnek proje üzerinden inceleyeceğiz.
İki aşamalı kimlik doğrulama nedir?
İki aşamalı kimlik doğrulama, kullanıcı güvenliğini sağlama adına oluşturulan ektsra bir katmandır. Başarılı bir doğrulama için kullanıcı adı(telefon numarası, email de olabilir) ve parolanın doğrulanmasının ardından ekstra bir doğrulama yaparak sisteme kabul edilmesini içerir.
İki aşamalı bir doğrulama barındırmayan uygulamalarda genellikle aşağıdaki akış takip edilir;
İki aşamalı doğrulama aktif edildikten sonra ise aşağıdaki gibi bir akış takip edilir;
Bu yaklaşım, geleneksel yönteme göre güvenliği artırır. Çünkü kötü niyetli bir saldırganın ele geçirmesi gereken bilgi sayısı artar. Ayrıca saldırganın yalnızca kullanıcı adı ve şifreyi bilmesi yetmez; kullanıcının örneğin telefon gibi tercih ettiği iletişim kanalını ele geçirmesi ve tüm bu işlemleri, doğrulama kodunun geçerlilik süresi içinde yapması gerekir.
Peki hangi yöntem ile kodu kullancı ile paylaşalım?
Yazılım dünyasında doğrulama kodunu kullanıcıya bir çok yöntem ile ulaştırabilirsiniz fakat en yaygın kullanılan iki tanesini örnek verecek olursak;
- SMS veya email tabanlı gönderim.
Bu yöntem, sıklıkla kullanılır ve muhtemelen daha önce karşılaşmışsınızdır. Sistem tarafından üretilen doğrulama kodu, SMS veya e-posta gibi iletişim kanalları üzerinden kullanıcıya gönderilir. Bu kodlar, belirli bir süre sonunda geçerliliğini yitirecek şekilde sunucu tarafında ayarlanır.
- TOTP ve HOTP Algoritmalarını Kullanarak Doğrulama Uygulaması Tabanlı Gönderim
Bu yaklaşımda doğrulama kodları algoritmik olarak oluşturulur. Kullanıcıların bu yöntemi kullanabilmesi için tercih ettikleri güvenlik uygulamasında (örneğin Google Authenticator) bir kurulum yapmaları gerekir. En yaygın yöntem, kullanıcıya sağlanan bir QR kodunu güvenlik uygulamasına taratarak her giriş denemesinde uygulamadan kod almasını sağlamaktır. Böylece kod, kullanıcı cihazında çevrimdışı olarak da üretilebilir.
İki yöntem hakkında genel bir kanı oluşturduğumuza göre TOTP ve HOTP mekanizmalarından da bahsederek örnek projeye devam edebiliriz.
TOTP (Time-Based One-Time Password)
TOTP, adından anlaşılacağı üzere zamana dayalı kodlar üretir. Örneğin, her 30 saniyede yeni bir kod oluşturulur ve kod yalnızca o 30 saniyelik süre boyunca geçerlidir.
Algoritmanın iki temel ihtiyacı bulunur. Birincisi, kodun her cihazda aynı anda oluşturulabilmesi için kullanılan zaman damgası (Unix timestamp); ikincisi ise, her kullanıcıya özel olarak tanımlanan ‘secret key’ adı verilen gizli anahtardır. Bu iki bileşen, kodların benzersiz ve güvenli bir şekilde oluşturulmasını sağlar.
Bu süreçte, sunucu ve kullanıcının cihazındaki uygulama (örneğin Google Authenticator) aynı ‘secret key’ ve zamanı kullanarak kod üretir. Kullanıcı giriş yapmaya çalıştığında, uygulamada görünen kod sunucuya gönderilir ve sunucu da aynı kodu üretmişse doğrulama sağlanır. Bu sayede, yalnızca belirli bir süre geçerli olan, kullanıcıya özel bir doğrulama kodu oluşur.
HOTP (HMAC-Based One-Time Password)
HOTP, sayaç tabanlı bir doğrulama kodu oluşturma algoritmasıdır. Kodlar, her doğrulama isteğinde bir sayaç değeri kullanılarak üretilir ve yalnızca bir kez kullanılmak üzere geçerlidir.
Algoritmanın iki temel ihtiyacı vardır. Birincisi, her doğrulama isteğinde artan ve kodun benzersiz olmasını sağlayan bir sayaç değeri; ikincisi ise, her kullanıcıya özel olarak tanımlanan ‘secret key’ adı verilen gizli anahtardır. Bu iki bileşen, her oturum açma girişiminde farklı ve tek kullanımlık bir kod oluşturulmasını sağlar.
Bu süreçte, sunucu ve kullanıcının cihazındaki uygulama (örneğin, bir doğrulama uygulaması veya donanım tokenı), aynı ‘secret key’ ve sayaç değeriyle kodu üretir. Kullanıcı giriş yaparken, doğrulama kodunu sunucuya gönderir; sunucu aynı ‘secret key’ ve sayaç değerini kullanarak aynı kodu oluşturmuşsa doğrulama başarılı olur. Kod yalnızca bir kez geçerli olduğundan, sonraki giriş denemelerinde yeni bir kod üretilmesi gerekir.
HOTP, süre sınırlaması olmadan çalışan bir algoritma olduğu için bazı durumlarda kullanışlıdır. Örneğin, kullanıcıların çevrimdışı olduğu veya zaman senkronizasyonunun zor olduğu durumlarda tercih edilebilir. Ancak, HOTP’nin güvenlik açısından bir zayıflığı vardır: Kodlar zamana bağlı olarak değil, yalnızca sayaç değeri ile oluşturulur ve süre sınırlamaları yoktur. Bu da, bir kod ele geçirilirse, kullanılana kadar geçerli olacağı anlamına gelir.
Algoritmaları irdeledikten sonra örnek projeye geçebiliriz.
Demo proje.
Demo projede, en yalın haliyle Django ile bir iki aşamalı kimlik doğrulama uygulaması geliştireceğiz. Öncelikle uygulamanın ekran görüntülerini paylaşarak yapıyı görsel olarak tanıyalım; ardından, kritik iş akışlarını ve uygulamanın güvenlik süreçlerini açıklayacağız.
Kodun tamamını, GitHub reposu üzerinden temin edebilir ve detaylıca inceleyebilirsiniz.
Uygulama Akışı ve Yetenek Seti
Projemiz şu yeteneklere sahip olacak:
- Kullanıcı Kaydı: Kullanıcılar sisteme kayıt olabilir.
- Giriş ve Kurulum Yönlendirmesi: Kayıtlı bir kullanıcı giriş yapmaya çalıştığında, eğer iki aşamalı kimlik doğrulama kurulumunu tamamlamadıysa QR kodunu taratarak kurulumunu ilerletebileceği sayfaya yönlendirilecek.
- İki Aşamalı Kimlik Doğrulama: Kurulumu tamamlanmış kullanıcılar, her girişte iki aşamalı kimlik doğrulamasını geçerek uygulamaya erişim sağlayacak.
- Özel İçerik Erişimi: İki aşamalı kimlik doğrulamasını başarıyla tamamlayan kullanıcılar, uygulamanın “secret” sayfasına erişebilecekler.
Anasayfa.
OTP Kurulum Sayfası.
Secret Page.
Uygulamaya da bir aşinalık kazandığımıza göre ilerleyebiliriz.
İlk olarak Django’nun varsayılan kullanıcı modelini genişleterek, iki aşamalı doğrulama için gerekli olacak secret_key gibi bilgileri tutacak olan OTPConfiguration modelini oluşturalım.
# users/models.py import pyotp from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ class OTPConfigurationStatus(models.TextChoices): NOT_INITIALIZED = "not_initialized", _('Not initialized') INITIALIZATION_STARTED = "initialization_started", _('Initialization started') COMPLETED = "completed", _('Completed') FAILED = "failed", _("Failed") class OTPConfiguration(models.Model): user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE) status = models.CharField( max_length=30, choices=OTPConfigurationStatus.choices, default=OTPConfigurationStatus.NOT_INITIALIZED ) secret_key = models.CharField(max_length=255) @property def totp(self): return pyotp.TOTP(s=self.secret_key)
Bu modelde konfigürasyonun hangi durumda bulunduğunu status ve her kullanıcının TOTP algoritmasına bileşen olarak sağlayabilmesi adına secret_key alanını saklıyoruz.
pyOTP kütüphanesi yardımıyla da TOTP nesnesini oluşturarak model üzerinden erişilebilir hale getiriyoruz.
Devam etmeden önce kısaca pyOTP ve TOTP nesnesinin özelliklerine kısaca gözatacak olursak;
pyOTP, Python programlama dilinde HOTP ve TOTP algoritmalarıyla kod oluşturma, kod doğruluğunu kontrol etmenizi sağlayan bir işlevsel ve kullanımı fazlasıyla kolay bir paket.
import pyotp import time totp = pyotp.TOTP('base32secret3232') totp.now() # => '492039' # OTP verified for current time totp.verify('492039') # => True time.sleep(30) totp.verify('492039') # => False
Yukarıdaki snippetta oluşturulan TOTP nesnesi üzerinden bir kodun nasıl üretilebileceği ve doğrulamasının nasıl yapılabileceğini görebiliriz.
Eğer TOTP nesnesini init ederken interval değerini farklı bir tam sayı belirtseydik kod geçerlilik süresi değişmiş olacaktı.
Bunun yanında pyOTP, QR kod üretilebilmesini sağlamak için otpauth formatında URI’ da üretebilir. Bu URİ ile üretilmiş olan QR kod taratıldığında, uygulamanın sağlamış olduğu tüm gerekli bilgiler kullanıcı cihazındaki güvenlik uygulamasına bildirilmiş olur.
>>>totp.provisioning_uri(issuer_name="ProviderAppName", name="blog-post-username") 'otpauth://totp/ProviderAppName:blog-post-username?secret=base32secret3232&issuer=ProviderAppName'
Login işleminde OTP kurulum sürecini başlatalım.
#users/views.py from django.conf import settings from django.contrib.auth.views import LoginView as BaseLoginView from django.shortcuts import redirect from django.urls import reverse_lazy class UserLoginView(BaseLoginView): template_name = 'users/login.html' def form_valid(self, form): user = form.get_user() otp_configuration = getattr(user, 'otpconfiguration', None) if not otp_configuration or otp_configuration.status != OTPConfigurationStatus.COMPLETED: self.request.session[settings.OTP_SETUP_SESSION_USER_PK_KEY] = user.pk return redirect(reverse_lazy('users:setup_two_factor_auth')) else: self.request.session[settings.OTP_VERIFY_SESSION_USER_PK_KEY] = user.pk return redirect(reverse_lazy('users:otp_verify'))
Var olan LoginView
‘i genişleterek, kullanıcı adı ve parolasını doğrulamış kullanıcıyı OTP kurulumunu yapması veya kurulum tamamlandıysa kodu doğrulaması için ilgili sayfaya yönlendiriyoruz.
Bu yapı, kullanıcı giriş bilgilerinin doğruluğunu onayladıktan sonra, asıl login işlemini gerçekleştirmeden önce OTP’nin doğrulanmasını zorunlu kılmakta. Kullanıcının pk
bilgisini anonim oturum verisine yazıyoruz ki, yönlendirdiğimiz diğer view’larda pk ile hangi kullanıcı üzerinde çalıştığımızı saptayabilelim ve yine diğer viewlarda bu session keyi yoksa erişimi kısıtlayacağız ki akış dışı erişimi engelleyebilelim.
SetupTwoFactorAuthView
ile kurulumu tamamlayalım.
Bu view, iki aşamalı kimlik doğrulamanın kurulum adımını iki taraflı olarak yönetecek. Kullanıcı ilk kez giriş yaparken, sistemde OTP kurulumunu tamamlamamışsa bu view ile QR kodunu tarayıp oluşan ilk kodu yine sisteme geri bildirecek ve kurulumu ilerletecek.
Bu view üzerindeki işlemleri sıralayacak olursak;
- Kullanıcıyı Tanımlama ve OTP Kaydını Oluşturma: Kullanıcı, LoginView üzerinden gelen session key kullanılarak saptanacak.
OTPConfigurationService
yardımıyla kullanıcı için birOtpConfiguration
nesnesi oluşturacağız (daha önce oluşturulmamışsa). Bu yapılandırma, kullanıcıya özel birsecret_key
içerecek.
Kullanıcının güvenlik uygulamasında (örneğin Google Authenticator) tanıtabilmesi içinpyOTP
yardımıyla bir URI oluşturacağız. Bu URI ve kullanıcının gireceği ilk kodu kabul edecek formu da contexte bağlayarak sayfayı render edeceğiz.
Öncelikle view’ın üzerinden yukarıdaki maddede açıklanan işlemleri nasıl yaptığına göz atalım;
class SetupTwoFactorAuthView( OTPSetupSessionKeyRequiredMixin, SuccessMessageMixin, FormView, ): service = OTPConfigurationService() form_class = OTPCodeVerifyForm template_name = 'users/setup_2fa.html' success_url = reverse_lazy('index') success_message = _('Two-factor authentication has been successfully enabled.') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) user = self.get_user_with_session_key() otp_configuration = self.service.get_or_create_otp_configuration(user) provisioning_uri = self.service.get_provisioning_uri(otp_configuration, name=user.username) context["provisioning_uri"] = provisioning_uri return context def get_user_with_session_key(self): return User.objects.get(pk=self.request.session.get(settings.OTP_SETUP_SESSION_USER_PK_KEY)) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["user"] = self.get_user_with_session_key() return kwargs
Service kısmıda aşağıdaki şekilde;
class OTPConfigurationService: def get_or_create_otp_configuration(self, user, **kwargs): otp_configuration, _ = OTPConfiguration.objects.get_or_create( user=user, **kwargs, defaults={'status': OTPConfigurationStatus.INITIALIZATION_STARTED, 'secret_key': pyotp.random_base32()}, ) return otp_configuration def get_provisioning_uri( self, otp_configuration: OTPConfiguration, name: str, issuer_name: Optional[str] = None ) -> str: if not issuer_name: issuer_name = settings.APP_NAME return otp_configuration.totp.provisioning_uri(name=name, issuer_name=issuer_name) ...
Bunun yanında OTPCodeVerifyForm
‘ındada render edilerek kullanıcının kurulumu tamamlaması için cihazında üretilen ilk kodu kontrol girmesini beklediğimizi ve kontrolünün yapılacağından bahsetmiştik.
Form içeriği;
# users/forms.py from django import forms from users.service import OTPConfigurationService class OTPCodeVerifyForm(forms.Form): code = forms.CharField(max_length=6, help_text='Enter code after first scan.') def clean_code(self): code = self.cleaned_data["code"] otp_configuration = self.user.otpconfiguration service = OTPConfigurationService() if service.check_code_is_valid(otp_configuration, code=code): return code raise forms.ValidationError('Your code is mismatched. Please try again.') def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs)
OTPConfigurationService
sınıfıysa TOTP nesnesi üzerinden kod geçerliliğini aşağıdaki gibi kontrol etmekte.
# users/service.py class OTPConfigurationService: def check_code_is_valid(self, otp_confiuration: OTPConfiguration, code: str): return otp_confiuration.totp.verify(code) ...
QR kodunu kullanıcıya gösterebilmek adına QRCode.js kütüphanesinden yararlanıyoruz. Bu kod yalnızca bir seferliğine render edilecek.
# users/setup_2fa.html {% block extra_js %} https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js <script> const otpUrl = '{{ provisioning_uri|escapejs }}' new QRCode(document.getElementById('qrcode'), { text: otpUrl, width: 150, height: 150, colorDark: '#000000', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.H }) </script> {% endblock %}
Birinci işlemi tamamladığımıza göre ikinci adımı açıklayabiliriz.
2. OTP Kurulumunu Tamamlama ve Kullanıcıyı Sisteme Kabul Etme.
Kullanıcı, eğer cihazı üzerinde kurulumu tamamlayıp koda ulaşmış ve doğru koduda sistemle paylaşmıssa artık sisteme kabul edilip, kendini doğruladığı bilgisi üzerine eklenmelidir ki doğrulama gerektiren sayfalara yada enpointlere erişimini sağlayabilsin.
Bu işlemide view’ın form_valid
metodunda aşağıdaki gibi gerçekleştiriyoruz.
class SetupTwoFactorAuthView( OTPSetupSessionKeyRequiredMixin, SuccessMessageMixin, FormView, ): service = OTPConfigurationService() form_class = OTPCodeVerifyForm template_name = 'users/setup_2fa.html' success_url = reverse_lazy('index') success_message = _('Two-factor authentication has been successfully enabled.') def form_valid(self, form): user = self.get_user_with_session_key() otp_configuration = user.otpconfiguration self.service.update_otp_configuration(instance=otp_configuration, status=OTPConfigurationStatus.COMPLETED) del self.request.session[settings.OTP_SETUP_SESSION_USER_PK_KEY] auth_login(self.request, user) self.request.session["is_verified"] = True return super().form_valid(form) def get_user_with_session_key(self): return User.objects.get(pk=self.request.session.get(settings.OTP_SETUP_SESSION_USER_PK_KEY))
Kullanıcıyı yine anonim sessionu üzerindeki key yardımıyla saptadıktan sonra, OTPConfiguration statüsünü tamamlandı olarak işaretliyor, tekrar setup view’ına erişememesi adına anonim sessionı üzerinden setup keyini kaldırıyoruz.
Ardından Django’nun yerleşik login
metodu sayesinde sisteme kabul edip oturum açmasını sağladığımız kullanıcıyı is_verified
olarak işaretliyoruz.
OtpVerifiedMiddleware
ile Doğrulamayı Sürekli Çevrimde Kontrol Edelim.Sisteme kabul edilen kullanıcının session’ı üzerine yazmış olduğumu is_verified
flagini middleware üzerinde aşağıdaki gibi kullanıcı üzerine bağlayarak doğrulama sürecini tamamlayalım.
Aşağıdaki gibi bir middleware yapısı isterimizi karşılayacaktır;
# users/middleware.py class OtpVerifiedMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if request.user.is_authenticated: is_verified = request.session.get("is_verified", False) request.user.is_verified = is_verified response = self.get_response(request) return response
Bu middleware’i de varolan AuthenticationMiddleware
‘in hemen altına ekliyoruz ki request nesnesi üzerine user’ın atanmış olduğundan emin olalım.
MIDDLEWARE = [ ... "django.contrib.auth.middleware.AuthenticationMiddleware", "users.middleware.OtpVerifiedMiddleware", ... ]
Middlewareler hakkında daha derinlemesine bilgi adına okuma yapmak isterseniz.
SecretPageView
üzerinde erişimi kısıtlama.
Secret page için kullanıcının yalnızca authenticated olması değil aynı zamanda is_verified
olması da gerektiğinden Django’nun yerleşik UserPassesTestMixin
mixinini kullanarak erişimi denetleyebiliriz.
class OtpVerifiedRequiredMixin(UserPassesTestMixin): def test_func(self): return getattr(self.request.user, 'is_verified', False) def handle_no_permission(self): return redirect(reverse_lazy(settings.LOGIN_URL)) class SecretPageView(LoginRequiredMixin, OtpVerifiedRequiredMixin, TemplateView): template_name = 'core/secret.html'
Belirtilen koşula uymayan bir kullanıcının istekte bulunduğunda, login sayfasına yönlendirilerek kendini doğrulaması sağlanacaktır.
Kapanış ve değerlendirme.
Öncelikle umarım bu blogu okumak sizin için hem öğretici hem de keyifli olmuştur.
Genel hatlarıyla iki aşamalı kimlik doğrulamanın neden önemli olduğundan, hangi bileşenlere sahip olduğundan ve bu yapıyı nasıl uygulayabileceğimizden demo bir projede bahsettik. Temel mantığı kavramak ve en yalın haliyle iki aşamalı doğrulama akışı oluşturmak üzerine çalıştık.
Bu demo proje, şu andaki temel haliyle doğrudan canlıya geçmeye hazır olmasa da, kafanızda bazı önemli sorular oluşturduysa başarılı olabilmiş demektir: “Ya kullanıcı cihazını kaybederse?”, “Kullanıcı birden fazla cihazla oturum açmaya çalışırsa?”, “Secret key’i açık şekilde saklamak güvenli mi?”
İşte bu sorular, daha güvenli ve sağlam bir iki aşamalı doğrulama süreci geliştirme yolunda bizi araştırmaya teşvik edecektir. Eğer daha fazlasını öğrenmek ve daha kompleks, güvenliği daha yüksek çözümler oluşturmak istiyorsanız, django-otp paketine göz atabilirsiniz.
django-otp, kullanıcıların birden fazla cihaz kullanmasını, cihazlarını kaybetmesi gibi durumları yönetmemizi sağlar, farklı doğrulama yöntemlerini ve ihtiyaç duyulabilecek temel bileşenleri sunarak tekerleği tekrar icat ettirmeden hızlıca kullanmamıza olanak sağlar.
Bazı faydalı linkleri de liste halinde burada bulabilirsiniz.
- Demo projenin Github adresi.
- django-otp paketinin Github adresi.
- Vikipedi iki aşamalı kimlik doğrulama sayfası.
Başka blog yazılarında görüşmek üzere!
İlk Yorumu Siz Yapın