Drupal 8 ve React Native

Drupal, PHP tabanlı bir açık kaynak kodlu içerik yönetim sistemidir. React Native, JavaScript ve React'i kullanarak yerel uygulamalar oluşturmak için kullanılan bir framework.

Neden React Native?

Bu günlerde kullanabileceğiniz sayısız ön uç teknolojileri bulunmaktadır. En popüler olanları ise Angular ve React’tır. Her iki teknolojide  de uygulamaları oluşturmanıza olanak sağlıyor, fakat en önemli farkı uygulamaların nasıl oluşturulacağıdır.

React Native'i kullanmanın avantajı, JavaScript'i yerel kodu dönüştürürken JavaScript oluşturarak bir uygulama inşa etmenizi sağlar. Aksine, Angular veya İonic, temelde bir web görünümüne gömülmüş bir web sitesi olan hibrid uygulama oluşturmanıza izin verir. Buna rağmen cihazın native özelliklerine ulaşabilirsiniz.

Bu durumda, React Native'i tercih ediyoruz, çünkü native olarak çalışan iOS ve Android uygulamaları oluşturmak istiyoruz.

 

Arayüzden Bağımsız Drupal

Genelde, Arayüzden Bağımsız Drupal'da, ziyaretçi Angular.js veya Backbone.js gibi Javascript çerçeveleri kullanılarak oluşturulan sayfaları görecektir. Geleneksel bir Drupal temasını göremeyecekler.

Bu durumda, Drupal ağırlıklı olarak veri deposu olarak kullanılır ve daha sonra çerçeve tarafından okunur. Normal Drupal arayüzü, kullanıcılar tarafından web sitesinde görünecek içeriği girmeye devam etmektedir.

Bununla birlikte, normal Drupal arayüzü bile bir mobil uygulama gibi diğer araçlarla değiştirilebilir. Bu durumda, Drupal yalnızca web uygulamasını back-end'e çalıştırılma mantığını sağlar.

Bu örnekte, verilerini bir Drupal web sitesinden alan native bir iOS ve Android uygulamasını nasıl kuracağınızı keşfedeceksiniz. Bu bilgilere erişmek için kullanıcıların uygulamada oturum açmaları gerekir; bu durumda, uygulamanın kullanıcının tercihlerine uygun içeriği sunmasına izin verir. 

Drupal 8 React native

Yani bu zaten bizi ilk sorunumuzla karışı karşıya getiriyor. Native bir uygulama kullandığımız için, çerezler veya oturumlar yoluyla kullanıcıların kimlik doğrulaması yapmak mümkün değildir. Bu nedenle size React Native uygulamanızı ve Drupal sitenizi kimliği doğrulanmış istekleri kabul edecek şekilde nasıl hazırlayacağınızı göstereceğiz.
 

Yapı Mimarisi

Mimari, bir vanilya Drupal 8 sürümünden ve Redux'la React Native projesinden oluşuyor.

Uygulanan akış aşağıdaki gibidir:

  • Bir kullanıcı, uygulamada sunulan giriş ekranını açar.
  • Kullanıcı formundaki kimlik bilgilerini doldurur.
  • Uygulama, kimlik bilgilerini Drupal'daki son noktaya gönderir.
  • Drupal kimlik bilgilerini doğrular ve kullanıcıyı günlüğe kaydeder.
  • Drupal, geçerli kullanıcıya dayalı bir token ile yanıt verir.
  • Uygulama, gelecekte kullanmak üzere token'ı saklar.
  • Uygulama artık, uygulamanın Drupal REST API'sına yaptığı diğer tüm isteklerde token'ı kullanır.

 

Drupal'da Bir Son Nokta Oluşturma

Önce kimlik doğrulama yöntemimizi seçmek zorundaydık. Bu örnekte, bir JWT veya JSON web belirteci kullanarak kimlik doğrulamayı seçtik, çünkü Drupal.org üzerinde zaten büyük bir katkıda bulunan bir modül mevcuttur. (https://www.drupal.org/project/jwt)

Bu modül, artık Drupal 8 çekirdek olan REST modülü ile kullanabileceğiniz bir kimlik doğrulama hizmeti sunmaktadır. Bu kimlik doğrulama hizmeti, isteğin başlıklarından geçen token'ı okuyacak ve buradaki geçerli kullanıcıyı belirleyecektir. Ardından, Drupal'daki tüm sonraki işlevler, o kullanıcıyı, istenen kaynaklara erişme izninin olup olmadığını belirlemek için kullanacaktır. Bu kimlik doğrulama hizmeti, sonraki tüm istekler için çalışır, ancak orijinal JWT isteği isteği için geçerli değildir.

JWT modülünün sağladığı orijinal bitiş noktası, kullanıcının token'ı olarak hizmet etmeden önce oturum açmasını beklemektedir. Kullanılabilir hazır temel kimlik doğrulama servisini kullanabilirsiniz, ancak örnek olarak kendi servisimizi kurmayı tercih ettik.

 

JSON Post Kimlik Doğrulama

Temel kimlik doğrulama hizmetinin beklediği gibi, istek başlığındaki kullanıcı adı ve parolayı geçirmek yerine, kullanıcı adı ve parolasını JSON olarak biçimlendirilen isteğimizin gövdesine göndereceğiz.

Kimlik doğrulama sınıfımız AuthenticationProviderInterface uygulamasını uygular ve json_web_token.services.yml dosyasında aşağıdaki şekilde eklenir:

services:
 authentication.json_web_token:
   class: Drupal\json_web_token\Authentication\Provider\JsonAuthenticationProvider
   arguments: ['@config.factory', '@user.auth', '@flood', '@entity.manager']
   tags:
     - { name: authentication_provider, provider_id: 'json_authentication_provider', priority: 100 }

Arabirim, iki yöntem uygulamak zorunda olduğumuzu, uygulandığını ve kimliğini doğruladığını belirtiyor:

public function applies(Request $request) {
 
 $content = json_decode($request->getContent());
 
 return isset($content->username, $content->password) && !empty($content->username) && 
 !empty($content->password);
}

Burada, kimliği doğrulayıcının ne zaman uygulanması gerektiğini tanımlıyoruz. Dolayısıyla, gereksinim duyduğumuz JSON'un bir kullanıcı adı ve parola içermesi gerekmektedir. Diğer tüm durumlarda, bu kimlik doğrulayıcı atlanabilir. Tanımladığınız her kimlik doğrulama hizmeti her zaman Drupal tarafından çağrılır. Bu nedenle, kimlik doğrulama hizmetini uygulama koşullarınızı tanımlamanız çok önemlidir.

public function authenticate(Request $request) {
 $flood_config = $this->configFactory->get('user.flood');
 $content = json_decode($request->getContent());
 
 $username = $content->username;
 $password = $content->password;
 // Flood protection: this is very similar to the user login form code.
 // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
 // Do not allow any login from the current user's IP if the limit has been
 // reached. Default is 50 failed attempts allowed in one hour. This is
 // independent of the per-user limit to catch attempts from one IP to log
 // in to many different user accounts.  We have a reasonably high limit
 // since there may be only one apparent IP for all users at an institution.
 if ($this->flood->isAllowed(json_authentication_provider.failed_login_ip', 
$flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
   $accounts = $this->entityManager->getStorage('user')
     ->loadByProperties(array('name' => $username, 'status' => 1));
   $account = reset($accounts);
   if ($account) {
     if ($flood_config->get('uid_only')) {
       // Register flood events based on the uid only, so they apply for any
       // IP address. This is the most secure option.
       $identifier = $account->id();
     }
     else {
       // The default identifier is a combination of uid and IP address. This
       // is less secure but more resistant to denial-of-service attacks that
       // could lock out all users with public user names.
       $identifier = $account->id() . '-' . $request->getClientIP();
     }
     // Don't allow login if the limit for this user has been reached.
     // Default is to allow 5 failed attempts every 6 hours.
     if ($this->flood->isAllowed('json_authentication_provider.failed_login_user',

$flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
       $uid = $this->userAuth->authenticate($username, $password);
       if ($uid) {
         $this->flood->clear('json_authentication_provider.failed_login_user',

$identifier);
         return $this->entityManager->getStorage('user')->load($uid);
       }
       else {
         // Register a per-user failed login event.
         $this->flood->register('json_authentication_provider.failed_login_user', 
$flood_config->get('user_window'), $identifier);
       }
     }
   }
 }
 
 // Always register an IP-based failed login event.
 $this->flood->register('json_authentication_provider.failed_login_ip',
 $flood_config->get('ip_window'));
 return [];
}

Burada, verileri bir JSON biçiminden okuduğumuzun farkıyla temel yetkilendirme hizmetinin kimlik doğrulama işlevselliğini yeniden gözden geçirdik. Bu kod, kullanıcıyı Drupal uygulamasına kaydeder. 

JWT Token Alma

JWT token almak için REST modülünü kullandık ve yeni bir rest kaynağı eklentisi oluşturduk. Modülün zaten sağladığı bitiş noktasını kullanmış olabiliriz, ancak tüm bitiş noktalarımızı bir versiyonuyla oluşturmayı tercih ediyoruz. Eklentiyi aşağıdaki açıklama ile tanımladık:

/**
* Provides a resource to get a JWT token.
*
* @RestResource(
*   id = "token_rest_resource",
*   label = @Translation("Token rest resource"),
*   uri_paths = {
*     "canonical" = "/api/v1/token",
*     "https://www.drupal.org/link-relations/create" = "/api/v1/token"
*   }
* )
*/

Uri_paths bu ek açıklamanın en önemli parçasıdır. Standart ve garip görünen Drupal.org anahtarlarını ayarlayarak, son nokta için tamamen özel bir yol ayarlayabiliyoruz. Bu, API'mizin sürümünü URI'de şöyle ayarlamamızı sağlar: / api / v1 / token. Bu şekilde, API'mızın yeni sürümlerini kolayca yayabilir ve eski sürümlerin kullanımdan kaldırılması hakkında net bir şekilde iletişim kurabiliriz.

Sınıfımız, REST modülü tarafından sağlanan ResourceBase sınıfını genişletir. Sınıfımızda yalnızca bir post yöntemi uyguladık ki sadece bu bitiş noktasının mesajlar üzerinde çalışmasını istiyoruz.

public function post() {
 
 if($this->currentUser->isAnonymous()){
   $data['message'] = $this->t("Login failed. If you don't have an account register. 
If you forgot your credentials please reset your password.");
 }else{
   $data['message'] = $this->t('Login succeeded');
   $data['token'] = $this->generateToken();
 }
 
 return new ResourceResponse($data);
}
 
/**
* Generates a new JWT.
*/
protected function generateToken() {
 $token = new JsonWebToken();
 $event = new JwtAuthIssuerEvent($token);
 $this->eventDispatcher->dispatch(JwtAuthIssuerEvents::GENERATE, $event);
 $jwt = $event->getToken();
 
 return $this->transcoder->encode($jwt, array());
}

GenerateToken yöntemi, bize geri dönebilen bir simge elde etmek için JWT modülünü kullandığımız özel bir yöntemdir.

Doğrudan bir JSON nesnesi döndürmüyoruz. Bir dizi şeklinde yanıt gönderiyoruz. Bu, REST modülünün kullanışlı bir özelliği, çünkü bitiş noktanızın biçimlerini Drupal'daki arayüzü kullanarak seçebilmemize olanak sağlıyor. Böylece xml, JSON veya hal_json gibi diğer desteklenen biçimleri kolayca geri döndürebilirsiniz. Bu örnek için hal_json'ı seçtik.

Drupal, güvenli olmayan yöntemler için bazı yerleşik güvenlik önlemleri içermektedir. Güvenli yöntemler HEAD, GET, OPTIONS ve TRACE'dir. Güvenli olmayan bir yöntem uyguladığımız için aşağıdaki hususları hesaba katmalıyız:

Uygulama bir yayın gönderdiğinde, çapraz site isteği sahteciliğini önlemek için başlıkta bir X-CSRF-Token'i de göndermesi gerekir. Bu simge / session / token bitiş noktasından edinilebilir.
POST durumunda, "_format = hal_json" sorgu parametresinin üstünde Content-type istek başlığını "application / hal + json" olarak ayarlamamız gerekiyor.

 

Bir Şeyleri Bir Araya Getirmek

Geriye kalan tek şey, rest modüllerinin / admin / config / services / rest üzerinde sağladığı arabirim üzerinden uç noktamızı etkinleştirmektir.

Güncelleme:  Bu genellemeyi edinmek için Rest UI modülünü (https://www.drupal.org/project/restui) indirmeniz ve etkinleştirmeniz gerekir.

Drupal 8 React Native

Gördüğünüz gibi, jeton_adını özel json_authentication_provider servisimiz ile yapılandırdık ve hal_json ve json formatlarında kullanıma sunuyoruz.

Güncelleme: https://github.com/shaksi/json_web_token

 

React Native uygulamamızda uç noktayı çağırma
GİRİŞ BİLEŞENİ

Giriş bileşenimiz iki giriş alanı ve bir düğme içermektedir.
 

<Item rounded style={styles.inputGrp}>
   <Icon name="person"/>
   <Input
       placeholder="Username"
       onChangeText={username => this.setState({username})}
       placeholderTextColor="#FFF"
       style={styles.input}
   />
</Item>
 
<Item rounded style={styles.inputGrp}>
   <Icon name="unlock"/>
   <Input
       placeholder="Password"
       secureTextEntry
       placeholderTextColor="#FFF"
       onChangeText={password => this.setState({password})}
       style={styles.input}
   />
</Item>
 
<Button
   rounded primary block large
   style={styles.loginBtn}
   onPress={() => this.login({
       username: this.state.username,
       password: this.state.password
   })}
>
   <Text style={Platform.OS === 'android' ? {
       fontSize: 16,
       textAlign: 'center',
       top: -5
   } : {fontSize: 16, fontWeight: '900'}}>Get Started</Text>
</Button>

Oturum açma düğmesini tıkladığımızda, bindActions işlevimizde tanımlanan oturum açma eylemini tetikliyoruz.

function bindActions(dispatch) {
   return {
       login: (username, password) => dispatch(login(username, password)),
   };
}

Giriş işlemi auth.js dosyamızda tanımlanmıştır: 

import type { Action } from './types';
import axios from 'react-native-axios';
 
export const LOGIN = 'LOGIN';
 
export function login(username, password):Action {
 
   var jwt = '';
  
   var endpoint = "https://example.com/api/v1/token?_format=hal_json";
  
   return {
       type: LOGIN,
       payload: axios({
           method: 'post',
           url: endpoint,
           data:  {
               username: username,
               password: password,
               jwt: jwt,
           },
           headers: {
               'Content-Type':'application/hal+json',
               'X-CSRF-Token':'V5GBdzli7IvPCuRjMqvlEC4CeSeXgufl4Jx3hngZYRw'
           }
       })
   }
}

Bu örnekte, basit tutmak için X-CSRF-simgesini sabit olarak ayarladık. Normalde bunu ilk elde edersiniz. Ayrıca, yayınımıza cevap vermek için react-native-axios paketini kullandık. Bu eylem bir söz verir. Redux Store'da vaat ve thunk katman yazılımını kullanırsanız, redüktörünüzü şu şekilde kurabilirsiniz.

import type { Action } from './types';
import axios from 'react-native-axios';
 
export const LOGIN = 'LOGIN';
 
export function login(username, password):Action {
 
   var jwt = '';
  
   var endpoint = "https://example.com/api/v1/token?_format=hal_json";
  
   return {
       type: LOGIN,
       payload: axios({
           method: 'post',
           url: endpoint,
           data:  {
               username: username,
               password: password,
               jwt: jwt,
           },
           headers: {
               'Content-Type':'application/hal+json',
               'X-CSRF-Token':'V5GBdzli7IvPCuRjMqvlEC4CeSeXgufl4Jx3hngZYRw'
           }
       })
   }
}

Redüktör, sözün farklı eylem tipleri üzerinde hareket edebilecek:

LOGIN_PENDING: Bileşeni durumunu değiştirmenize izin verir, böylece bir simge oluşturmaya çalışırken bir yükleyici uygulayabilirsiniz.
LOGIN_REJECTED: Deneme başarısız olduğunda, neden başarısız olduğunu bildiren bir bildirimde bulunabilirsiniz.
LOGIN_FULFILLED: Deneme başarılı olduğunda, token'ınız var ve durumu oturum açacak şekilde ayarladınız.
Bu yüzden hepsini uyguladıktan sonra ana içerik deposu olarak bir Drupal 8 sitesini kullanan bir iOS ve Android uygulaması bulduk.

Bu örneği takiben hangi kullanıcı platformunda olursa olsun, kullanıcılarınıza özel içerik sunacak şekilde ayarlamış olmalısınız.

Bu makalenin amacı, yaklaşmakta olan iOS veya Android uygulaması için Drupal 8'in ne kadar etkili bir kaynak olabileceğini göstermekti.

Kaynaklar: