Je souhaite obtenir des informations sur une formation complète concernant le thème DIVI dispensé
par un organisme de formation certifié par l’état.
Que la formation soit finançable par mon CPF (idéalement) ou autre


Sanctuaire est le package d’authentification API léger de Laravel. dans le mon dernier articleJ’ai essayé d’authentifier un React SPA en utilisant une API Laravel via Sanctum. Ce tutoriel décrit comment utiliser Laravel Sanctum pour authentifier une application mobile. L’application est installée battement, La boîte à outils de développement d’applications multiplateformes de Google. Je peux ignorer certains des détails de mise en œuvre de l’application mobile, car ce n’est pas l’objet de ce didacticiel.

Vous pouvez trouver des liens vers le code final à la fin de cet article.

Le backend

J’ai configuré Homestead pour servir un nom de domaine. api.sanctum-mobile.testoù mon backend est fourni, ainsi qu’une base de données MySQL.

Tout d’abord, créez l’application Laravel:

laravel new sanctum_mobile

Au moment de la rédaction, cela me donne un nouveau projet Laravel (v8.6.0). Comme pour le didacticiel SPA, l’API fournit une liste de livres, je vais donc créer les mêmes ressources:

php artisan make:model Book -mr

le mr Les indicateurs créent également la migration et le contrôleur. Avant d’entrer dans les migrations, installons d’abord le package Sanctum car nous avons à nouveau besoin des migrations.

composer require laravel/sanctum
php artisan vendor:publish --provider="LaravelSanctumSanctumServiceProvider"

Créez maintenant le books Migration:

Schema::create('books', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('author');
    $table->timestamps();
});

Ensuite, exécutez les migrations de votre application:

php artisan migrate

Maintenant, si vous regardez la base de données, vous pouvez voir que la migration de Sanctum en a créé une personal_access_tokens Table que nous utiliserons plus tard lors de l’authentification de l’application mobile.

Mettons à jour DatabaseSeeder.php pour nous donner quelques livres (et un utilisateur pour plus tard):

Book::truncate();
$faker = FakerFactory::create();
for ($i = 0; $i < 50; $i++) {
    Book::create([
        'title' => $faker->sentence,
        'author' => $faker->name,
    ]);
}
User::truncate();
User::create([
    'name' => 'Alex',
    'email' => '[email protected]',
    'password' => Hash::make('pwdpwd'),
]);

Maintenant, définissez la base de données: php artisan db:seed. Enfin, créez la route et l’action du contrôleur. Ajoutez ceci à la routes/api.php Déposer:

Route::get('book', [BookController::class, 'index']);

puis dans le index Méthode de BookControllerpour retourner tous les livres:

return response()->json(Book::all());

Après avoir vérifié que le point de terminaison fonctionne – curl https://api.sanctum-mobile.test/api/book – Il est temps de lancer l’application mobile.

L’application mobile

Pour l’application mobile, nous utilisons Android Studio et battement. Flutter vous permet de créer des applications multiplateformes qui réutilisent le même code sur les appareils Android et iPhone. Suivez d’abord les instructions d’installation de Flutter et à Configurer Android StudioEnsuite, lancez Android Studio et cliquez sur « Créer un nouveau projet Flutter ».

Suivez également la recette du livre de cuisine de Flatter Récupérer des données sur Internet pour créer une page qui extrait une liste de livres de l’API. Un moyen rapide et facile de rendre notre API disponible sur l’appareil Android Studio consiste à utiliser Homesteads share Commander:

share api.sanctum-mobile.test

La console en produit un jupe Page qui vous donne une URL (quelque chose comme https://0c9775bd.ngrok.io) Mettez votre serveur local à la disposition du public. (Une alternative à ngrok est Beyond Code’s Exposer.) Alors faisons-en un utils/constants.dart Fichier pour mettre cela dans:

const API_URL = 'http://191b43391926.ngrok.io';

Revenons maintenant au livre de recettes Flutter. Créer un fichier books.dart qui contient les classes requises pour notre liste de livres. Premier Book Classe pour stocker les données de la requête API:

class Book {
    final int id;
    final String title;
    final String author;
    Book({this.id, this.title, this.author});
    factory Book.fromJson(Map<String, dynamic> json) {
        return Book(
            id: json['id'],
            title: json['title'],
            author: json['author'],
        );
    }
}

Deuxième un BookList Classez pour obtenir les livres et allez chez le constructeur pour les voir:

class BookList extends StatefulWidget {
    @override
    _BookListState createState() => _BookListState();
}

class _BookListState extends State<BookList> {
    Future<List<Book>> futureBooks;

    @override
    void initState() {
        super.initState();
        futureBooks = fetchBooks();
    }

    Future<List<Book>> fetchBooks() async {
        List<Book> books = new List<Book>();
        final response = await http.get('$API_URL/api/book');
        if (response.statusCode == 200) {
            List<dynamic> data = json.decode(response.body);
            for (int i = 0; i < data.length; i++) {
                books.add(Book.fromJson(data[i]));
            }
            return books;
        } else {
            throw Exception('Problem loading books');
        }
    }

    @override
    Widget build(BuildContext context) {
        return Column(
            children: <Widget>[
                BookListBuilder(futureBooks: futureBooks),
            ],
        );
    }
}

Et enfin un BookListBuilder pour voir les livres:

class BookListBuilder extends StatelessWidget {
    const BookListBuilder({
        Key key,
        @required this.futureBooks,
    }) : super(key: key);

    final Future<List<Book>> futureBooks;

    @override
    Widget build(BuildContext context) {
        return FutureBuilder<List<Book>>(
            future: futureBooks,
            builder: (context, snapshot) {
                if (snapshot.hasData) {
                    return Expanded(child: ListView.builder(
                        itemCount: snapshot.data.length,
                        itemBuilder: (context, index) {
                            Book book = snapshot.data[index];
                            return ListTile(
                                title: Text('${book.title}'),
                                subtitle: Text('${book.author}'),
                            );
                        },
                    ));
                } else if (snapshot.hasError) {
                    return Text("${snapshot.error}");
                }
                return CircularProgressIndicator();
            }
        );
    }
}

Maintenant nous devons juste changer cela MyApp Classe dans main.dart pour charger le BookList::

class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Sanctum Books',
            home: new Scaffold(
                body: BookList(),
            )
        );
    }
}

Maintenant, démarrez-le sur votre appareil de test ou votre émulateur et vous devriez voir une liste de livres.

Authentification avec Sanctum

Excellent, donc nous savons que l’API fonctionne et que nous pouvons en tirer des livres. L’étape suivante consiste à configurer l’authentification.

Je vais utiliser ça fournisseurs Emballez et suivez les directives de la documentation officielle de l’établissement gestion simple des statuts. Je souhaite créer un fournisseur d’authentification qui suivra l’état de connexion et finira par communiquer avec le serveur. Créer un nouveau fichier, auth.dart. La fonction d’authentification est utilisée ici. Pour le moment nous reviendrons true Voici comment nous pouvons tester le fonctionnement du processus:

class AuthProvider extends ChangeNotifier {
    bool _isAuthenticated = false;

    bool get isAuthenticated => _isAuthenticated;

    Future<bool> login(String email, String password) async {
       print('logging in with email $email and password $password');
        _isAuthenticated = true;
        notifyListeners();
        return true;
    }
}

Avec ce fournisseur, nous pouvons maintenant vérifier que nous sommes authentifiés et afficher la bonne page en conséquence. Change toi main Fonction pour impliquer le fournisseur:

void main() {
    runApp(
        ChangeNotifierProvider(
            create: (BuildContext context) => AuthProvider(),
            child: MyApp(),
        )
    );
}

… et changez-les MyApp Super de les montrer BookList Widget lorsque nous sommes connectés, ou un LoginForm Sinon widget:

body: Center(
    child: Consumer<AuthProvider>(
        builder: (context, auth, child) {
            switch (auth.isAuthenticated) {
                case true:
                    return BookList();
                default:
                    return LoginForm();
            }
        },
    )
),

le LoginForm Les classes contiennent beaucoup de cruft « widgety », donc je vais vous référencer le dépôt GitHub si vous êtes intéressé à le voir. Lorsque vous chargez l’application sur votre appareil de test, un formulaire d’inscription devrait apparaître. Entrez une adresse e-mail et un mot de passe aléatoires, soumettez le formulaire et vous verrez une liste de livres.

Ok, configurons le backend pour l’authentification. Les documents Dites-nous pour créer une route qui accepte le nom d’utilisateur et le mot de passe, ainsi qu’un nom de périphérique, et pour renvoyer un jeton. Alors créons un itinéraire dans le api.php Déposer:

Route::post('token', [AuthController::class, 'requestToken']);

et un contrôleur: php artisan make:controller AuthController. Celui-ci contient le code des documents:

public function requestToken(Request $request): string
{
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
}

Si le nom d’utilisateur et le mot de passe sont valides, un jeton est créé, stocké dans la base de données et renvoyé au client. Pour que cela fonctionne, nous devons ajouter que HasApiTokens Caractéristique de notre User Modèle. Cela nous donne un tokens Relation qui nous permet de créer et de récupérer des jetons pour l’utilisateur, et un createToken Méthode. Le jeton lui-même est un sha256 Hash d’une chaîne aléatoire de 40 caractères: Cette chaîne (non effacée) est renvoyée au client, qui doit la sauvegarder pour l’utiliser pour les futures requêtes à l’API. Plus précisément, la chaîne renvoyée au client se compose de l’ID du jeton suivi d’un caractère pipe (|) suivi du jeton de texte brut (non flou).

Nous avons maintenant configuré ce point de terminaison. Mettons à jour l’application pour l’utiliser. le login La méthode doit maintenant publier le email, password, et device_name à ce point de terminaison et s’il en obtient un 200 Enregistrez le jeton dans la mémoire de l’appareil. Au device_nameJ’utilise ça package device_info Afin d’obtenir l’ID unique du périphérique, cependant, cette chaîne de caractères est arbitraire.

final response = await http.post('$API_URL/token', body: {
    'email': email,
    'password': password,
    'device_name': await getDeviceId(),
}, headers: {
    'Accept': 'application/json',
}); 

if (response.statusCode == 200) {
    String token = response.body;
    await saveToken(token);
    _isAuthenticated = true;
    notifyListeners();
}

J’utilise ça shared_preferences paquet, avec lesquelles de simples paires clé-valeur peuvent être enregistrées pour stocker le jeton:

saveToken(String token) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('token', token);
}

Maintenant, l’application affiche la page du livre une fois que vous vous êtes connecté avec succès. Dans la perspective d’aujourd’hui, les livres sont bien sûr accessibles avec ou sans inscription réussie. Essayez-le: curl https://api.sanctum-mobile.test/api/book. Protégeons la route maintenant:

Route:::middleware('auth:sanctum')->get('book', [BookController::class, 'index']);

Connectez-vous à nouveau à l’application et cette fois j’obtiens un message d’erreur: « Problème lors du chargement des livres ». Ils s’authentifient avec succès, mais comme nous n’avons pas encore envoyé le jeton API avec notre demande d’obtenir les livres, l’API ne les envoie pas à juste titre. Comme dans le tutoriel précédent, jetons un coup d’œil au Sanctum garder pour voir ce qu’il fait ici:

if ($token = $request->bearerToken()) {
    $model = Sanctum::$personalAccessTokenModel;

    $accessToken = $model::findToken($token);

    if (! $accessToken ||
        ($this->expiration &&
         $accessToken->created_at->lte(now()->subMinutes($this->expiration))) ||
         ! $this->hasValidProvider($accessToken->tokenable)) {
        return;
    }

    return $this->supportsTokens($accessToken->tokenable) ? $accessToken->tokenable->withAccessToken(
        tap($accessToken->forceFill(['last_used_at' => now()]))->save()
    ) : null;
}

La première condition est ignorée car nous n’utilisons pas Web Guard. Ce qui nous laisse avec le code ci-dessus. Premièrement, elle ne sera exécutée que si la requête a un jeton « porteur », c’est-à-dire si elle contient un jeton Authorization En-tête commençant par la chaîne « Bearer ». Si c’est le cas, cela s’appelle findToken méthode sur le PersonalAccessToken Modèle:

if (strpos($token, '|') === false) {
    return static::where('token', hash('sha256', $token))->first();
}

[$id, $token] = explode('|', $token, 2);

if ($instance = static::find($id)) {
    return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null;
}

La première condition vérifie si le caractère de canal est dans le jeton et, dans le cas contraire, renvoie le premier modèle qui correspond au jeton. Je suppose que c’est pour aider à maintenir la compatibilité descendante avec les versions de Sanctum antérieures à 2.3 n’a pas inclus le caractère pipe dans le jeton de texte brut lors du retour à l’utilisateur. ((Voici la pull request: La raison était de rendre la requête de recherche de jeton plus puissante.) Quoi qu’il en soit, en supposant le caractère pipe est Là, Sanctum accède à l’ID de modèle et au jeton lui-même et vérifie si le hachage correspond à ce qui est stocké dans la base de données. Dans ce cas, le modèle est renvoyé.

De retour dans Guard: Si aucun jeton n’est retourné, ou si nous envisageons l’expiration du jeton (ce qui n’est pas le cas dans ce cas), retour null (Dans ce cas, l’authentification échouera). À la fin:

return $this->supportsTokens($accessToken->tokenable) ? $accessToken->tokenable->withAccessToken(
    tap($accessToken->forceFill(['last_used_at' => now()]))->save()
) : null;

Vérifiez si le tokenable Modèle (c’est-à-dire le User Model) prend en charge les jetons (en d’autres termes, qu’il utilise le HasApiTokens Caractéristique). Sinon, reviens null – L’authentification échoue. Si tel est le cas, retournez:

$accessToken->tokenable->withAccessToken(
    tap($accessToken->forceFill(['last_used_at' => now()]))->save()
)

L’exemple ci-dessus utilise la version à argument unique de tap Assistant. Cela peut être utilisé pour appliquer une méthode éloquente (dans ce cas save) pour renvoyer le modèle vous-même. Voici le modèle de jeton d’accès last_used_at L’horodatage est mis à jour. Le modèle enregistré est ensuite passé à la en tant qu’argument User Des modèles withAccessToken Méthode (qu’il obtient de la HasApiTokens Caractéristique). C’est un moyen compact de mettre à jour les jetons last_used_at Horodatage et retour de la User Modèle. Cela signifie que l’authentification a réussi.

Revenons donc à l’application. Avec cette authentification, nous devons mettre à jour l’appel de l’application vers le book Point de terminaison pour transmettre le jeton dans la demande Authorization Entête. Pour ce faire, mettez à jour le fetchBooks Méthode pour obtenir le jeton du Auth Provider, puis ajoutez-le à l’en-tête:

String token = await Provider.of<AuthProvider>(context, listen: false).getToken();
final response = await http.get('$API_URL/book', headers: {
    'Authorization': 'Bearer $token',
});

N’oubliez pas d’ajouter un getToken Méthode pour AuthProvider Classer:

Future<String> getToken() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('token');
}

Maintenant, essayez à nouveau de vous connecter. Cette fois, les livres devraient être affichés.

Le code final de l’API et de l’application se trouve ici (y compris les fonctions de déconnexion):



Source link

Recent Posts