React Native
Fullstack Mobil — Bölüm 3: JWT ile Authentication
API'mize JWT tabanlı kimlik doğrulama ekleyip RN tarafında tam auth akışını kuruyoruz. BCrypt şifre hash, AuthController, Axios interceptor ile token enjeksiyonu, Zustand store ile auth state — production seviyesinde güvenlik.
21 Nisan 2026
6 dk okuma
18
0
IDE tarafinda: Visual Studio tarafinda projeye sag tiklayip Add > New Scaffolded Item > Identity diyerek bu dialog acilir. Ozellestirmek istediginiz sayfalari isaretleyip ApplicationDbContext'i seciyoruz.
Merhaba arkadaşlar, serinin üçüncü bölümüne hoş geldiniz. Geçen yazıda CRUD API'mizi yazdık ama her şey açıktaydı — kim isterse herkesDELETE yapabilirdi. Bu bölümde bunu düzeltiyoruz: JWT tabanlı authentication kuruyoruz, kullanıcı kayıt/giriş akışını kodluyoruz, RN tarafında token'ı güvenli saklayıp her istekle API'ye göndereceğiz.Akışı Görselleştirelim
Login akışı şöyle:
1. Kullanıcı RN app'te e-posta + şifre girer
2. RN →
POST /api/auth/login — API bilgileri doğrular3. API doğruysa JWT token üretip döndürür
4. RN bu token'ı
AsyncStorage'a kaydeder5. Bundan sonraki her istekte
Authorization: Bearer <token> header'ı eklenir6. API her istekte token'ı doğrular,
[Authorize] attribute'u olan endpoint'leri korurBackend: Paketleri Ekleyelim
cd api
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package BCrypt.Net-Next
BCrypt şifre hash'leme için. Düz metin şifre asla saklanmaz.Auth Servisi
// Services/AuthService.cs
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
public interface IAuthService
{
Task<string?> LoginAsync(string email, string sifre);
Task<User> RegisterAsync(string kullaniciAdi, string email, string sifre);
}
public class AuthService(AppDb db, IConfiguration config) : IAuthService
{
public async Task<User> RegisterAsync(string kullaniciAdi, string email, string sifre)
{
var existing = await db.Users
.FirstOrDefaultAsync(u => u.Email == email || u.KullaniciAdi == kullaniciAdi);
if (existing is not null)
throw new InvalidOperationException("Bu e-posta veya kullanıcı adı zaten kayıtlı.");
var user = new User
{
KullaniciAdi = kullaniciAdi,
Email = email,
SifreHash = BCrypt.Net.BCrypt.HashPassword(sifre),
Rol = "User"
};
db.Users.Add(user);
await db.SaveChangesAsync();
return user;
}
public async Task<string?> LoginAsync(string email, string sifre)
{
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == email);
if (user is null) return null;
if (!BCrypt.Net.BCrypt.Verify(sifre, user.SifreHash)) return null;
return TokenUret(user);
}
private string TokenUret(User user)
{
var key = config["Jwt:Key"]!;
var issuer = config["Jwt:Issuer"]!;
var audience = config["Jwt:Audience"]!;
var expireMin = int.Parse(config["Jwt:ExpireMinutes"] ?? "60");
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, user.KullaniciAdi),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(ClaimTypes.Role, user.Rol),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var creds = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(expireMin),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
AuthController// Controllers/AuthController.cs
public record RegisterDto([Required] string KullaniciAdi, [Required, EmailAddress] string Email, [Required, MinLength(6)] string Sifre);
public record LoginDto([Required] string Email, [Required] string Sifre);
public record AuthResponseDto(string Token, string KullaniciAdi, string Rol);
[ApiController]
[Route("api/[controller]")]
public class AuthController(IAuthService auth) : ControllerBase
{
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterDto dto)
{
try
{
var user = await auth.RegisterAsync(dto.KullaniciAdi, dto.Email, dto.Sifre);
return Ok(new { user.Id, user.KullaniciAdi, user.Email });
}
catch (InvalidOperationException ex)
{
return Conflict(new { error = ex.Message });
}
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginDto dto)
{
var token = await auth.LoginAsync(dto.Email, dto.Sifre);
if (token is null) return Unauthorized(new { error = "Geçersiz bilgiler" });
// Rolü tekrar çekmek yerine token'dan parse edip döneriz — pratikte servisten dönsün daha iyi
return Ok(new AuthResponseDto(token, dto.Email, "User"));
}
}
Program.cs'te Auth Pipelineusing Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
builder.Services.AddScoped<IAuthService, AuthService>();
var jwtKey = builder.Configuration["Jwt:Key"]!;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)),
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
// ...
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
Sıra geldi endpoint'leri korumaya:[Authorize]
[HttpPost]
public async Task<IActionResult> Olustur([FromBody] PostCreateDto dto)
{
var userId = int.Parse(User.FindFirstValue(JwtRegisteredClaimNames.Sub)!);
var post = new Post
{
Baslik = dto.Baslik,
Icerik = dto.Icerik,
YazarId = userId,
Yayinda = false
};
db.Posts.Add(post);
await db.SaveChangesAsync();
return CreatedAtAction(nameof(Getir), new { id = post.Id }, new { id = post.Id });
}
[Authorize(Roles = "Admin")]
[HttpDelete("{id:int}")]
public async Task<IActionResult> Sil(int id) { ... }
Frontend: Mobile Tarafında AuthRN tarafında AsyncStorage kullanacağız:
cd mobile
npx expo install @@react-native-async-storage/async-storage
npm install axios zustand
Axios Instance + Interceptor// src/api/client.ts
import axios from 'axios';
import AsyncStorage from '@@react-native-async-storage/async-storage';
export const api = axios.create({
baseURL: __DEV__ ? 'http://192.168.1.42:5000' : 'https://api.blogapp.com',
timeout: 15000
});
// Her istekte token'ı ekle
api.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 401 gelirse token'ı temizle
api.interceptors.response.use(
(r) => r,
async (error) => {
if (error.response?.status === 401) {
await AsyncStorage.removeItem('authToken');
}
return Promise.reject(error);
}
);
Auth Store — Zustand// src/store/auth.ts
import { create } from 'zustand';
import AsyncStorage from '@@react-native-async-storage/async-storage';
import { api } from '../api/client';
type AuthState = {
token: string | null;
kullaniciAdi: string | null;
rol: string | null;
yuklendi: boolean;
login: (email: string, sifre: string) => Promise<void>;
logout: () => Promise<void>;
baslat: () => Promise<void>;
};
export const useAuth = create<AuthState>((set) => ({
token: null,
kullaniciAdi: null,
rol: null,
yuklendi: false,
baslat: async () => {
const token = await AsyncStorage.getItem('authToken');
const ka = await AsyncStorage.getItem('kullaniciAdi');
const rol = await AsyncStorage.getItem('rol');
set({ token, kullaniciAdi: ka, rol, yuklendi: true });
},
login: async (email, sifre) => {
const res = await api.post('/api/auth/login', { email, sifre });
await AsyncStorage.setItem('authToken', res.data.token);
await AsyncStorage.setItem('kullaniciAdi', res.data.kullaniciAdi);
await AsyncStorage.setItem('rol', res.data.rol);
set({ token: res.data.token, kullaniciAdi: res.data.kullaniciAdi, rol: res.data.rol });
},
logout: async () => {
await AsyncStorage.multiRemove(['authToken', 'kullaniciAdi', 'rol']);
set({ token: null, kullaniciAdi: null, rol: null });
}
}));
Login Ekranı// src/screens/LoginScreen.tsx
import { useState } from 'react';
import { View, Text, TextInput, Button, Alert, StyleSheet } from 'react-native';
import { useAuth } from '../store/auth';
export function LoginScreen() {
const [email, setEmail] = useState('');
const [sifre, setSifre] = useState('');
const [yukleniyor, setYukleniyor] = useState(false);
const login = useAuth((s) => s.login);
const handleLogin = async () => {
setYukleniyor(true);
try {
await login(email, sifre);
} catch (err: any) {
Alert.alert('Hata', err.response?.data?.error ?? 'Giriş yapılamadı');
} finally {
setYukleniyor(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.baslik}>Giriş Yap</Text>
<TextInput
placeholder="E-posta"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
style={styles.input}
/>
<TextInput
placeholder="Şifre"
value={sifre}
onChangeText={setSifre}
secureTextEntry
style={styles.input}
/>
<Button title={yukleniyor ? 'Giriş yapılıyor...' : 'Giriş'} onPress={handleLogin} disabled={yukleniyor} />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 20, backgroundColor: '#0f172a' },
baslik: { fontSize: 32, fontWeight: 'bold', color: '#fff', marginBottom: 30, textAlign: 'center' },
input: { backgroundColor: '#1e293b', color: '#fff', padding: 14, borderRadius: 8, marginBottom: 12, fontSize: 16 }
});
Güvenlik Notları- AsyncStorage şifreli değil: Jailbreak edilmiş telefonlarda okunabilir. Hassas veri için expo-secure-store kullanın.
- HTTPS zorunlu: Production'da asla HTTP kullanmayın, token çalınır.
- Token refresh: JWT'nin süresi dolunca refresh token pattern ile yenileme yapın.
- Logout temizliği: Sadece state'i değil storage'ı da temizleyin.
Özet
Bu bölümde full auth akışı kurduk: backend'de BCrypt + JWT, mobilde Axios interceptor + Zustand store + AsyncStorage. Artık korumalı endpoint'lerimiz ve girişli kullanıcı deneyimimiz var.
Bir sonraki bölümde UI tarafına yoğunlaşıyoruz: Navigation, ekran akışları, React Query ile data fetching, liste + detay + form ekranları. Başka bir makalede görüşmek üzere...
Yorumlar (0)
Henüz yorum yok. İlk yorumu sen yap!