Laravel API — JWT Examples

Tolga Karabulut
7 min readJan 7, 2020

--

Bugün Laravel üzerinden bir API( Application Programming Interface ) uygulaması geliştireceğiz ve güvenliği JWT üzerinden sağlayacağız. Öncelikli olarak JWT nedir? bu konuya değinelim.

JWT ( JSON WEB TOKEN)

JWT IETF kuruluşu tarafından standartlaştırılmış bir Token biçimidir. Standart ile ilgili detaylı bilgiye https://jwt.io/introduction/ üzerinden erişebilirsiniz. JWT haberleşen sistemler arasında ( örn. Mobil , Web , Cloud , IOT, vb. ) kullanıcıyı tanıma , doğrulama ve bilgi güvenliği vb. konularda kullanılır.

JWT’nin Yapısı

JWT temel olarak Base64 ile kodlanmış 3 ayrı parçadan oluşmaktadır. Bu parçalar birbirinden “ . ” ile ayrılır. Bu parçalar

  • Header
  • Payload
  • Signature

Header

JWT ye ait tanımlama bilgilerinin bulunduğu kısımdır. Standart olarak şu şekilde tanımlanır.

{
"alg": "HS256",
"typ": "JWT"
}
/**
Alg : burada JWT için Şifreleme Algoritmasını belirler. Kullandığınız dile bağlı olarak desteklenen Algoritmalar değişkenlik gösterir. jwt.io üzerinden desteklenen Algoritmaları inceleyebilirsiniz.
Typ : Kullanılan Header tipinin JWT olduğunu ifade eder.
**/

Payload

Burada bazı standartlar bulunsada genel olarak taşımak istediğimiz verileri içeren kısımdır. Standart bazı anahtarlardan bahsetmek gerekirse bunlar;

  • iss (issuer): Yayıncı
  • sub (subject): Konu
  • exp (expiration time): Son kullanım tarihi
  • nbf (not before time): Bu Tarihten önce
  • iat (issued at time): Oluşturulduğu tarih

Bu kadarı yeterli çok daha fazla kullanılan standart anahtarlar mevcut ilerleyen zamanlarda bu alanlardan da bahsediyor oluruz.

Signature

Bu kısım JWT oluşturulurken kullanılan Anahtarın bulunduğu kısımdır. Başlık da belirtilen şifreleme metodu ile şifrelenir.

~~~

Ürettiğiniz JWT nin signature kısmı hariç olmak üzere içinde veri okunabilir. Base64 decode yapmanız yeterli olacaktır. Fakat içeriğinde ki bilgide bir değişiklik yapamazsınız Anahtar kullanılmaz hale gelecektir.

JWT Avantajları

  • Durumsuz (stateless) oldukları için kullanıcıların bilgisi için veritabanı işlemlerine gerek kalmaz.
  • Oturum yönetimi içi çerezler olmadan yapılabilir.
  • Tek bir anahtar birden fazla sunucu üzerinde çalışabilir.
  • Veritabanı vb. işlemler yapılmadığı için çok daha hızlıdır.

Dezavantajları

  • Gizli anahtarınız yeterince kuvvetli değilse eğer kolaylıkla manipüle edilebilir.
  • Durumsuz oldukları için sunucu tarafında geçersiz kılmanın bir yolu yoktur. ( Birkaç yöntem ile çalışmasını engelleyebiliyoruz. )

Genel hatları ile JWT den bahsettik. Şimdi basit bir Laravel API üzerinde nasıl çalıştığını inceleyelim. Genel olarak iki farklı kütüphane kullanılmaktadır. Bunlar

  • firebase/php-jwt
  • tymon/jwt-auth

Biz uygulamamız üzerinde “ tymon/jwt-auth ” kullanacağız. Öncelikli olarak Composer ile projemize dahil ediyoruz.

composer require tymon/jwt-auth:1.0.0

Projeyi Laravel 6 ile geliştiriyoruz bu anlatacağım kısım 5.4 ve üzeri için geçerlidir daha alt sürümlerde dokümantasyon üzerinden bilgi alabilirsiniz. Paketi yükledikten sonra Artisan CLI üzerinden

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

komutunu çalıştırıyoruz. Sonrasında :app/config/ içerisinde jwt.php oluştuğunu göreceksiniz. Daha sonrasında JWT için şifrenin belirlenmesi adımına ilerliyoruz. Yine terminal üzerinden

php artisan jwt:secret

komutunu çalıştırıyoruz. Sonrasında .env dosyanızda JWT_SECRET anahtarı oluşmuş olacak isterseniz buradan anahtarı istediğiniz gibi değiştirebilirsiniz. Örnek bir uygulama olduğu için veritabanı bağlantısı varmış gibi anlatacağım. Standart olarak gelen User model üzerinden anlatacağım siz isterseniz kendi Modeliniz üzerinden çalıştırabilirsiniz bir fark olmayacaktır. Öncelikli olarak /App/User.php üzerinde şu değişiklikleri yapıyoruz.

use Tymon\JWTAuth\Contracts\JWTSubject;
...
class User extends Authenticatable implements JWTSubject{public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
...

bu değişikliklerin ardından model dosyamız şu şekilde görünüyor olmalı ;

<?phpnamespace App;use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
*
@return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
*
@return array
*/
public function getJWTCustomClaims() : array
{
return [];
}
/**
* The attributes that are mass assignable.
*
*
@var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
*
@var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
*
@var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}

Şimdi kullanıcı giriş, detay, çıkış gibi işlemleri için bir rota oluşturalım. Router içinde ki api.php şöyle görünüyor olmalı;

# router/api.php
.....
Route::group([ 'middleware' => ['api'],
'prefix' => 'member'
], static function ($router) {
Route::post('login', 'MemberController@login');
Route::post('register', 'MemberController@register');
Route::group(['middleware' => 'jwt.verify'], static function( $router){
Route::post('logout', 'MemberController@logout');
Route::post('refresh', 'MemberController@refresh');
Route::get('detail', 'MemberController@detail');
});
});
.....

ve ardından jwt.verify middleware ını oluşturalım bu sayede rotalara gelen istekleri kontrol edebileceğiz. Artisan CLI üzerinden

php artisan make:middleware JwtMiddleware

:/app/Http/Middleware altında JwtMiddleware isimli dosya oluşmuş olacak içeriğini ise şu şekilde düzenliyoruz.

<?php
namespace App\Http\Middleware;
use Closure;
use JWTAuth;
use Exception;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
class JwtMiddleware extends BaseMiddleware
{
/**
* Handle an incoming request.
*
*
@param \Illuminate\Http\Request $request
*
@param \Closure $next
*
@return mixed
*/
public function handle($request, Closure $next)
{
try {
$user = JWTAuth::parseToken()->authenticate();
if( !$user ) throw new Exception('User Not Found');
} catch (Exception $e) {
if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token Invalid',
'code' => 1
]
]
);
}else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token Expired',
'code' =>1
]
]
);
}
else{
if( $e->getMessage() === 'User Not Found') {
return response()->json([
"data" => null,
"status" => false,
"err_" => [
"message" => "User Not Found",
"code" => 1
]
]
);
}
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Authorization Token not found',
'code' =>1
]
]
);
}
}
return $next($request);
}
}

ve bu middleware ı rotalar üzerinde kullanabilmek için bunu kernel üzerinde tanımlıyoruz. :/app/Http/Kernel.php artık tamamen çalışır durumda rota problemlerimizi çözdüğümüze göre artık gelen istekleri işleyebiliriz.

...
protected $routeMiddleware = [
....
'jwt.verify' => \App\Http\Middleware\JwtMiddleware::class,
...

Rotaları karşılayacak olan kontrolleri oluşturalım. Yine Artisan CLI üzerinden bu işlemi yapabilir. Şu şekilde

php artisan make:controller MemberController

sonrasında /app/Http/Controller içerisinde MemberController oluşmuş olacak içeriğini şu şekilde değiştirelim.

...
use App\Http\Requests\RegisterRequest;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use JWTAuth;
use Hash;
use App\User;
use Tymon\JWTAuth\Exceptions\JWTException;
...
protected $data = [];/**
*
*
@return void
*/
public function __construct()
{
$this->data = [
'status' => false,
'code' => 401,
'data' => null,
'err' => [
'code' => 1,
'message' => 'Unauthorized'
]
];
}
/**
* Request->only metodu ile sadece parametre olarak
* email - password ü alıyoruz ve laravel içerisinde bulunan auth
* metoduna ait olan attempt fonksiyonu ile kullanıcı doğrulamasını yapıyoruz.
* kullanıcı doğru ise eğer kullanıcıya sonraki işlemlerinde kullanabilmesi için
* bir token dönüyoruz.
*
*
@param Request $request
*
@return JsonResponse
*/
public function login(Request $request): JsonResponse
{
$credentials = $request->only(['email', 'password']);
try {
if (!$token = JWTAuth::attempt($credentials)) {
throw new Exception('invalid_credentials');
}
$this->data = [
'status' => true,
'code' => 200,
'data' => [
'_token' => $token
],
'err' => null
];
} catch (Exception $e) {
$this->data['err']['message'] = $e->getMessage();
$this->data['code'] = 401;
} catch (JWTException $e) {
$this->data['err']['message'] = 'Could not create token';
$this->data['code'] = 500;
}
return response()->json($this->data, $this->data['code']);
}
/**
* Kullanıcı kayıt eden method burada kullanılan RegisterRequest daha önce
* anlatıldığı için detaylandırmıyorum.
*
@param RegisterRequest $request
*
@return JsonResponse
*/
public function register(RegisterRequest $request): JsonResponse
{
$user = User::create([
'name' => $request->post('name'),
'email' => $request->post('email'),
'password' => Hash::make($request->post('password'))
]);
$this->data = [
'status' => true,
'code' => 200,
'data' => [
'User' => $user
],
'err' => null
];
return response()->json($this->data, $this->data['code']);
}
/**
* Doğrulanmış olan kullanıcının detay bilgilerini getir.
*
*
@return JsonResponse
*/
public function detail(): JsonResponse
{
$this->data = [
'status' => true,
'code' => 200,
'data' => [
'User' => auth()->user()
],
'err' => null
];
return response()->json($this->data);
}
/**
* Kullanıcının çıkış işlemini yap ve token'ı kullanılamaz duruma getir.
*
@return JsonResponse
*/
public function logout(): JsonResponse
{
auth()->logout();
$data = [
'status' => true,
'code' => 200,
'data' => [
'message' => 'Successfully logged out'
],
'err' => null
];
return response()->json($data);
}
/**
* Son kullanma tarihi geçmiş olan JWT nin tekrar kullanılır hale gelmesi
* için yenileme işlemi.
*
*
@return JsonResponse
*/
public function refresh(): JsonResponse
{
$data = [
'status' => true,
'code' => 200,
'data' => [
'_token' => auth()->refresh()
],
'err' => null
];
return response()->json($data, 200);
}

sonrasında Exception Handler dosyamızda JWT ile ilgili hataları yakalıyoruz ve geri dönüşleri ayarlıyoruz. Tam olarak şöyle görünüyor olmalı;

....use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
...
/**
* Render an exception into an HTTP response.
*
*
@param Request $request
*
@param \Exception $exception
*
@return Response
*/
public function render($request, Exception $exception) :Response
{
if ($exception instanceof UnauthorizedHttpException) {
$preException = $exception->getPrevious();
if ($preException instanceof
TokenExpiredException) {
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token Expired',
'code' =>1
]
]
);
}
else if ($preException instanceof
TokenInvalidException) {
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token Invalid',
'code' => 1
]
]
);
} else if ($preException instanceof
TokenBlacklistedException) {
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token Blacklisted',
'code' => 1
]
]
);
}
if ($exception->getMessage() === 'Token not provided') {
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'Token not provided',
'code' => 1
]
]
);
}else if( $exception->getMessage() === 'User not found'){
return response()->json([
'data' => null,
'status' => false,
'err_' => [
'message' => 'User Not Found',
'code' => 1
]
]
);
}
}
return parent::render($request, $exception);
}

ve böylelikle artık postman üzerinden testlerimizi yapabiliriz. Öncelikle Artisan CLI üzerinden projeyi başlatıyoruz.

php artisan serve

sonrasında projemiz http://127.0.0.1:8000 üzerinde çalışır duruma geliyor. Bu makalede burada bitti bir sonraki makaleler de Laravel ile kullanıcı yetkilendirme işlemlerini inceleyeceğiz. Herkese keyifli kodlamalar..

Tolga Karabulut
tolga.karabulut@medianova.com
Medianova CDN — Senior PHP Developer

Örnek Proje kodları
Proje dosyaları ve Postman json dosyasına github üzerinden erişebilirsiniz.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Tolga Karabulut
Tolga Karabulut

Written by Tolga Karabulut

Software Development Specialist | @teknasyon Developer Team

No responses yet

Write a response