Direkt zum Inhalt

Drupal 8 und React Native

Drupal ist ein PHP-basiertes Open-Source-Content-Management-System. React Native ist ein Framework zur Erstellung nativer Anwendungen mit JavaScript und React.
Seyfettin Kahveci
Seyfettin Kahveci
10 minuten lesen
drupal-8-ve-react-native

Warum React Native?

Heutzutage gibt es unzählige Frontend-Technologien, die Sie verwenden können. Die beliebtesten sind Angular und React. Mit beiden Technologien können Sie Anwendungen erstellen, aber der wichtigste Unterschied besteht darin, wie Sie die Anwendungen erstellen.

Der Vorteil von React Native ist, dass Sie eine Anwendung erstellen können, indem Sie JavaScript in nativen Code konvertieren. Im Gegensatz dazu können Sie mit Angular oder Ionic eine hybride Anwendung erstellen, die im Grunde eine in eine Webansicht eingebettete Website ist. Trotzdem können Sie die nativen Funktionen des Geräts erreichen.

In diesem Fall bevorzugen wir React Native, weil wir iOS- und Android-Apps erstellen wollen, die nativ laufen.

Schnittstellenunabhängiges Drupal

Im Allgemeinen sieht der Besucher in Interface Independent Drupal Seiten, die mit Javascript-Frameworks wie Angular.js oder Backbone.js erstellt wurden. Er wird kein traditionelles Drupal-Theme sehen.

In diesem Fall wird Drupal hauptsächlich als Datenspeicher verwendet, der dann von dem Framework gelesen wird. Die normale Drupal-Oberfläche wird immer noch von den Benutzern verwendet, um die Inhalte einzugeben, die auf der Website erscheinen sollen.

Aber auch die normale Drupal-Schnittstelle kann durch andere Tools ersetzt werden, beispielsweise durch eine mobile Anwendung. In diesem Fall stellt Drupal nur die Logik zur Ausführung der Webanwendung für das Backend bereit.

In diesem Beispiel erfahren Sie, wie Sie eine native iOS- und Android-Anwendung einrichten, die ihre Daten von einer Drupal-Website abruft. Um auf diese Informationen zuzugreifen, müssen sich die Benutzer bei der App anmelden. In diesem Fall kann die App Inhalte bereitstellen, die den Präferenzen des Benutzers entsprechen.

Bild entfernt.

Damit sind wir auch schon bei unserem ersten Problem angelangt. Da wir eine native Anwendung verwenden, ist es nicht möglich, Benutzer über Cookies oder Sitzungen zu authentifizieren. Wir werden Ihnen daher zeigen, wie Sie Ihre React Native-Anwendung und Ihre Drupal-Site so vorbereiten, dass sie authentifizierte Anfragen akzeptieren.

Architektur des Gebäudes

Die Architektur besteht aus einer Vanilla Drupal 8 Version und einem React Native Projekt mit Redux.

Der angewandte Ablauf ist wie folgt:

  • Ein Benutzer öffnet den Anmeldebildschirm der Anwendung.
  • Der Benutzer füllt die Anmeldedaten in das Formular ein.
  • Die Anwendung sendet die Anmeldeinformationen an den Endpunkt in Drupal.
  • Drupal validiert die Anmeldedaten und meldet den Benutzer an.
  • Drupal antwortet mit einem Token, das auf dem aktuellen Benutzer basiert.
  • Die Anwendung speichert das Token für die zukünftige Verwendung.
  • Die Anwendung verwendet nun das Token in allen anderen Anfragen, die die Anwendung an die Drupal REST API stellt.

Erstellen eines Endpunkts in Drupal

Zunächst mussten wir unsere Authentifizierungsmethode wählen. In diesem Beispiel haben wir uns für die Authentifizierung mit einem JWT oder JSON-Web-Token entschieden, da es auf Drupal.org bereits ein Modul gibt, das einen wichtigen Beitrag leistet (https://www.drupal.org/project/jwt).

Dieses Modul bietet einen Authentifizierungsdienst, den Sie mit dem REST-Modul verwenden können, das jetzt zum Kern von Drupal 8 gehört. Dieser Authentifizierungsdienst liest das in den Headern der Anfrage übergebene Token und ermittelt den aktuellen Benutzer. Alle nachfolgenden Funktionen in Drupal verwenden dann diesen Benutzer, um festzustellen, ob er die Berechtigung hat, auf die angeforderten Ressourcen zuzugreifen. Dieser Authentifizierungsdienst funktioniert für alle nachfolgenden Anfragen, gilt aber nicht für die ursprüngliche JWT-Anfrage.

Der ursprüngliche, vom JWT-Modul bereitgestellte Endpunkt wartet darauf, dass sich der Benutzer anmeldet, bevor er als Token dient. Sie können jeden verfügbaren Standard-Authentifizierungsdienst verwenden, aber wir haben uns entschieden, unseren eigenen Dienst als Beispiel einzurichten.

JSON Post-Authentifizierung

Anstatt den Benutzernamen und das Kennwort in der Kopfzeile der Anfrage zu übergeben, wie es der grundlegende Authentifizierungsdienst erwartet, werden wir den Benutzernamen und das Kennwort im Körper unserer Anfrage als JSON formatiert senden.

Unsere Authentifizierungsklasse implementiert das AuthenticationProviderInterface und wird wie folgt in json_web_token.services.yml eingefügt:

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 }

Die Schnittstelle sieht vor, dass wir zwei Methoden anwenden müssen, nämlich apply und authenticate:

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

Hier legen wir fest, wann der Authentifikator angewendet werden soll. Daher muss das JSON, das wir benötigen, einen Benutzernamen und ein Passwort enthalten. In allen anderen Fällen kann dieser Authentifikator weggelassen werden. Jeder Authentifizierungsdienst, den Sie definieren, wird immer von Drupal aufgerufen. Daher ist es sehr wichtig, dass Sie Ihre Bedingungen für die Implementierung des Authentifizierungsdienstes festlegen.

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 []; }

Hier haben wir die Authentifizierungsfunktionalität des grundlegenden Autorisierungsdienstes wieder aufgegriffen, mit der Erkenntnis, dass wir die Daten aus einem JSON-Format lesen. Dieser Code registriert den Benutzer bei der Drupal-Anwendung.

JWT Token Abruf

Wir haben das REST-Modul verwendet, um das JWT-Token zu erhalten, und ein neues Rest-Ressource-Plugin erstellt. Wir hätten auch den Endpunkt verwenden können, den das Modul bereits bereitstellt, aber wir ziehen es vor, alle unsere Endpunkte mit einer Version davon zu erstellen. Wir haben das Plugin mit der folgenden Beschreibung definiert:

/** * 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 ist der wichtigste Teil dieser Annotation. Indem wir die standardmäßigen und seltsam aussehenden Drupal.org-Schlüssel festlegen, können wir einen völlig benutzerdefinierten Pfad für den Endpunkt festlegen. Dadurch können wir die Version unserer API in der URI wie folgt festlegen: / api / v1 / token. Auf diese Weise können wir neue Versionen unserer API leicht verbreiten und die Abkündigung älterer Versionen klar kommunizieren.

Unsere Klasse erweitert die vom REST-Modul bereitgestellte ResourceBase-Klasse. Wir haben in unserer Klasse nur eine post-Methode implementiert, damit dieser Endpunkt nur für Nachrichten funktioniert.

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()); }

Die Methode GenerateToken ist eine spezielle Methode, bei der wir das JWT-Modul verwenden, um ein Token zu erhalten, das an uns zurückgegeben werden kann.

Wir geben nicht direkt ein JSON-Objekt zurück. Wir senden eine Antwort in Form eines Arrays. Dies ist eine nützliche Funktion des REST-Moduls, da es uns erlaubt, die Formate unseres Endpunkts über die Schnittstelle in Drupal zu wählen. So können Sie problemlos xml, JSON oder andere unterstützte Formate wie hal_json zurückgeben. Für dieses Beispiel haben wir hal_json gewählt.

Drupal hat einige eingebaute Sicherheitsmaßnahmen für unsichere Methoden. Sichere Methoden sind HEAD, GET, OPTIONS und TRACE. Da wir eine unsichere Methode implementieren, müssen wir die folgenden Überlegungen berücksichtigen:

Wenn die Anwendung einen Broadcast sendet, muss sie auch ein X-CSRF-Token im Header senden, um Cross-Site Request Forgery zu verhindern. Dieses Token kann über den Endpunkt / session / token bezogen werden.

Im Falle von POST müssen wir den Header für den Inhaltstyp der Anfrage auf "application / hal + json" über dem Abfrageparameter "_format = hal_json" setzen.

Dinge zusammenfügen

Alles, was bleibt, ist die Aktivierung unseres Endpunkts über die Schnittstelle, die von den Rest-Modulen on / admin / config / services / rest bereitgestellt wird.

Update: Um diese Verallgemeinerung zu erhalten, müssen Sie das Rest UI Modul herunterladen und aktivieren (https://www.drupal.org/project/restui).

Bild entfernt.

Wie Sie sehen können, haben wir den token_name mit unserem benutzerdefinierten json_authentication_provider-Dienst konfiguriert und ihn in den Formaten hal_json und json verfügbar gemacht.

Update: https://github.com/shaksi/json_web_token

EINSATZKOMPONENTE

Unsere Eingabekomponente enthält zwei Eingabefelder und eine Schaltfläche.

<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>

Wenn wir auf die Login-Schaltfläche klicken, lösen wir die in unserer bindActions-Funktion definierte Login-Aktion aus.

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

Der Anmeldevorgang wird in unserer Datei auth.js definiert:

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'           }       })   } }

In diesem Beispiel haben wir der Einfachheit halber das X-CSRF-icon auf konstant gesetzt, das man normalerweise als erstes erhält. Wir haben auch das Paket react-native-axios verwendet, um auf unsere Veröffentlichung zu reagieren. Diese Aktion gibt ein Versprechen. Wenn Sie die Versprechen- und Thunk-Middleware im Redux Store verwenden, können Sie Ihren Reducer wie folgt einrichten.

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'           }       })   } }

Der Reduzierer kann auf verschiedene Arten von Aktionen des Versprechens reagieren:

LOGIN_PENDING: Ermöglicht es Ihnen, den Zustand der Komponente zu ändern, so dass Sie einen Loader anwenden können, wenn Sie versuchen, ein Symbol zu erstellen. 
LOGIN_REJECTED: Wenn der Versuch fehlschlägt, können Sie eine Benachrichtigung ausgeben, die Ihnen mitteilt, warum er fehlgeschlagen ist. 
LOGIN_FULFILLED: Wenn der Versuch erfolgreich war, haben Sie das Token erhalten und den Status auf login gesetzt.

Nachdem wir das alles implementiert haben, haben wir eine iOS- und Android-App entwickelt, die eine Drupal 8-Website als Hauptinhaltsrepository verwendet.

Nach diesem Beispiel sollten Sie in der Lage sein, Ihren Benutzern benutzerdefinierte Inhalte zu liefern, unabhängig davon, auf welcher Plattform sie sich befinden.

Der Zweck dieses Artikels war es, zu zeigen, wie Drupal 8 eine effektive Ressource für Ihre kommende iOS- oder Android-App sein kann.

Quellen:

REST-Grundlagen: https://www.drupal.org/docs/8/core/modules/rest/1-getting-started-rest-configuration-rest-request-fundamentals

Unsere Büros

Drupart Locations

Unsere Buros

Wiesbaden

Hinterbergstraße 27
65207 Wiesbaden
Deutschland

+49 (0) 6151 – 492 70 23

[email protected]

London

151 West Green Road, London, England

+44 203 815 6478

[email protected]

Newark

112 Capitol Trail Suite, A437 Newark DE, 19711

+1 (740) 666 6255

[email protected]

Istanbul

GOSB Teknopark Hi-Tech Bina 3.Kat B3 Gebze - KOCAELİ

+49 (0) 6151 – 492 70 23

[email protected]