Appearance
Authenticatie voor de chat applicatie
Inhoud
We gaan gebruikers van de chat eerst laten inloggen, waarvoor ze eerst een account moeten aanmaken. Dit account gaan we lokaal op het apparaat van de gebruiker opslaan. In de productieversie kunnen we de aanmeldgegevens van de gebruikers opslaan in een of andere database. In de tussentijd gebruiken we een library om de gebruikersnaam en het wachtwoord dus lokaal op te slaan.
Lokale opslag
Om gegevens lokaal op te slaan maken we gebruik van de volgende library: Expo secure store
Expo SecureStore is een eenvoudige en veilige manier om gevoelige gegevens in je React Native app op te slaan. Je kunt het zien als een kleine, versleutelde kluis voor belangrijke informatie in je app.
Wat het doet
- Slaat beveiligde sleutel-waardeparen op het apparaat op
- Versleutelt de gegevens zodat ze niet gemakkelijk toegankelijk zijn voor onbevoegde gebruikers
- Perfect voor het opslaan van tokens, wachtwoorden en andere gevoelige informatie
- Werkt op iOS en Android met dezelfde API
Belangrijkste kenmerken
- Versleuteling: Gegevens worden in versleuteld formaat opgeslagen
- Eenvoud: Gemakkelijk te gebruiken met slechts enkele methoden (setItemAsync, getItemAsync, deleteItemAsync)
- Alleen strings: Slaat alleen stringwaarden op (objecten moeten naar JSON geconverteerd worden)
- Groottebeperking: Beperkte opslagruimte (ongeveer 2MB in totaal)
- Async API: Alle bewerkingen geven Promises terug
Wanneer te gebruiken
- Voor het opslaan van authenticatietokens
- Voor het bewaren van gebruikersreferenties
- Voor het veilig bewaren van API-sleutels
- Voor het opslaan van gevoelige gebruikersinformatie
Take The Wheel
Installeer deze library in jouw project.
React Context
We gaan ervoor moeten zorgen dat de aanmeldgegevens beschikbaar zijn voor alle componenten. Om dit alles te beheren gaan we gebruik maken van de React context.
Take The Wheel
- Maak een map
providersin de root van je project - Maak een nieuw bestand met de naam
ChatContext.tsxaan in deze map - Importeer de volgende dependencies in dit bestand:
- createContext en useState vanuit
React - useRouter vanuit
expo-router - Alert vanuit
react-native - * als SecureStore vanuit
expo-secure-store;
- createContext en useState vanuit
createContext
Vervolgens gaan we de React context aanmaken zodat alle componenten gebruik kunnen maken van de gegevens.
Context initialiseren
ts
export const ChatContext = createContext(null);De Provider
Alles staat nu klaar om onze Provider aan te maken die de state variabelen gaat voorzien voor de applicatie. Dit doe je als volgt:
Provider aanmaken
ts
export const ChatProvider = (props) => {
// Initialiseer de router om te kunnen navigeren
// Maak een state variabele isLoggedIn aan om de login status op te volgen, standaard is deze false
// Maak een state variabele aan om de gebruikersnaam in op te slaan, standaard is dit een lege string
// Maak een toggleLogin functie aan om te schakelen tussen de statussen van ingelogd en uitgelogd
};Take The Wheel
Implementeer in deze provider het volgende:
- Initialiseer de router om te kunnen navigeren
- Maak een state variabele
isLoggedInaan om de login status op te volgen, standaard is deze false - Maak een state variabele
loggedUseraan om de gebruikersnaam in op te slaan, standaard is dit een lege string - Maak een asynchrone
toggleLoginfunctie aan om te schakelen tussen de statussen van ingelogd en uitgelogd- Controleer de inlogstatus: Gebruik de variabele
isLoggedInom de huidige inlogstatus van de gebruiker te controleren. - Uitloggen:
- Als de gebruiker is ingelogd, stel de waarde van
userLoggedInin op none met behulp vanawait SecureStore.setItemAsync('userLoggedIn', 'none'). - Zet de variabele
isLoggedInop false. - Maak de variabele
loggedUserleeg. - Toon een melding met de tekst 'Je bent uitgelogd' met behulp van
Alert.alert.
- Als de gebruiker is ingelogd, stel de waarde van
- Navigeren naar de inlogpagina:
- Als de gebruiker niet is ingelogd, navigeer dan naar de inlogpagina met behulp van
router.push('/login').
- Als de gebruiker niet is ingelogd, navigeer dan naar de inlogpagina met behulp van
- Controleer de inlogstatus: Gebruik de variabele
Ophalen van de gebruiker
Is de gebruiker ingelogd?
Vervolgens gaan we moeten te weten te komen of er een gebruiker is ingelogd wanneer de App wordt opgestart. Hiervoor gaan we een asynchrone functie getUser aanmaken.
Take The Wheel
Ophalen van inloggegevens:
- Gebruik
await SecureStore.getItemAsync('userLoggedIn')om de inloggegevens van de gebruiker op te halen en sla deze op in een variabeleresult.
Controleer de opgehaalde gegevens:
- Als
resultgelijk is aan 'none', betekent dit dat er geen gebruiker is ingelogd. Log dit naar de console met de tekst 'Er is geen gebruiker ingelogd'. - Als
resultundefined is, betekent dit dat de app voor de eerste keer wordt uitgevoerd. Maak de storage key aan metawait SecureStore.setItemAsync('userLoggedIn', 'none')en log dit naar de console met de tekst 'App voor de eerste keer gestart, geen gebruiker aanwezig'. - Als
resulteen andere waarde heeft, betekent dit dat een gebruiker is ingelogd. Werk de status bij doorsetIsLoggedIn(true)ensetLoggedUser(result)aan te roepen en log dit naar de console met de tekst 'ingelogde gebruiker' samen met de gebruikersnaam van de gebruiker.
Foutafhandeling:
- Gebruik een try-catch blok om eventuele fouten tijdens het ophalen van de gegevens af te handelen. Log eventuele fouten naar de console met de tekst 'Error getting user data:' samen met de error.
de Chatgebruiker ophalen
Daarna voegen we een asynchrone functie getChatUser toe die de chatgebruiker ophaalt. Dit is vergelijkbaar met de getUser functie, maar deze functie zal navigeren naar de login pagina als er geen ingelogde gebruiker is.
Take The Wheel
Ophalen van inloggegevens:
- Gebruik
await SecureStore.getItemAsync('userLoggedIn')om de inloggegevens van de gebruiker op te halen en sla deze op in een variabeleresult.
Controleer de opgehaalde gegevens:
- Als
resultgelijk is aan 'none' of undefined, betekent dit dat er geen gebruiker is ingelogd.- Log dit naar de console met de tekst 'Geen gebruiker ingelogd')
- Toon een melding met
Alert.alert('Je moet inloggen om te kunnen chatten'), en navigeer naar de inlogpagina.
- Als
resulteen andere waarde heeft, betekent dit dat een gebruiker is ingelogd. Log dit naar de console met de tekst 'Gebruiker ingelogd' samen met de gebruikersnaam van de gebruiker.
Foutafhandeling:
- Gebruik een try-catch blok om eventuele fouten tijdens het ophalen van de gegevens af te handelen. Log eventuele fouten naar de console met de tekst 'Error getting chat user:' samen met de error en navigeer naar de loginpagina.
De variabelen ter beschikking stellen
We hoeven nu alleen nog de waarden op te geven die beschikbaar moeten zijn in de context. In de return voegen we het ChatContext.Provider component toe. Alle waarden die we aan de provider toevoegen zullen beschikbaar zijn voor elk component dat de context gebruikt. We voegen daarom toe:
- toggleLogin,
- getUser,
- isLoggedIn,
- setIsLoggedIn,
- loggedUser
- getChatUser
Binnen dit component voegen we de children prop toe. Hiermee kunnen we deze waarden doorgeven aan alle child componenten.
Dit ziet er als volgt uit:
Het ChatContext component
tsx
return (
<ChatContext.Provider
value={{
toggleLogin,
getUser,
isLoggedIn,
setIsLoggedIn,
loggedUser,
getChatUser
}}
>
{props.children}
</ChatContext.Provider>
);Uitgewerkte code
tsx
import {createContext, useState} from 'react';
import {useRouter} from 'expo-router';
import * as SecureStore from 'expo-secure-store';
import {Alert} from 'react-native';
export const ChatContext = createContext(null);
export const ChatProvider = (props) => {
// Initialiseer de router om te kunnen navigeren
const router = useRouter();
// De staat om de login status op te volgen
const [isLoggedIn, setIsLoggedIn] = useState(false);
// De staat pù de gebruikersnaam in op te slaan
const [loggedUser, setLoggedUser] = useState('');
//toggle tussen de statussen van ingelogd en uitgelogd
const toggleLogin = async () => {
if (isLoggedIn) {
// Als de gebruiker ingelogd is, loggen we deze uit
await SecureStore.setItemAsync('userLoggedIn', 'none');
setIsLoggedIn(false);
setLoggedUser('');
Alert.alert('You have been logged out');
} else {
// Wanneer geen gebruiker ingelogd is navigeren we naar de login pagina.
router.push('/login');
}
}
const getUser = async () => {
try {
const result = await SecureStore.getItemAsync('userLoggedIn');
if (result === 'none') {
console.log('No user logged in');
} else if (result === undefined) {
await SecureStore.setItemAsync('userLoggedIn', 'none');
console.log('First time app launch, no user');
} else {
setIsLoggedIn(true);
setLoggedUser(result);
await SecureStore.setItemAsync('userLoggedIn', result);
console.log('Logged in user: ', loggedUser)
}
} catch (error) {
console.error('Error getting user data:', error);
}
};
const getChatUser = async () => {
try {
const result = await SecureStore.getItemAsync('userLoggedIn');
if (result === 'none' || result === undefined) {
console.log('No user logged in');
Alert.alert('You need to log in to chat');
router.push('/login');
} else {
console.log(`User signed in: ${result}`);
}
} catch (error) {
console.error('Error getting chat user:', error);
router.push('/login');
}
};
return (
<ChatContext.Provider
value={{
toggleLogin,
getUser,
isLoggedIn,
setIsLoggedIn,
loggedUser,
getChatUser
}}
>
{props.children}
</ChatContext.Provider>
);
};Implementatie in de Header
We gaan de context nu aanroepen en gebruiken in het Header component zodat we gebruik kunnen maken van de waarden uit de context.
Take The Wheel
Importeer:
- ChatContext
- useEffect en useContext vanuit React
Zorg dat je aan de
toggleLogin,getUserenisLoggedIndata kan door de context te initialiseren met deuseContexthook en geef de ChatContext mee als argument:
ts
const {toggleLogin, getUser, isLoggedIn} = useContext(ChatContext);Voeg vervolgens de
useEffecthook toe om ervoor te zorgen dat degetUserfunctie wordt uitgevoerd wanneer het component wordt weergegeven. Voeg ook deisLoggedInvariabele toe als dependency om ervoor te zorgen datuseEffectwordt uitgevoerd wanneer deze waarde wordt gewijzigd, bijvoorbeeld wanneer een gebruiker uitlogt.Zorg ervoor dat de kleur van de login icoon wijzigt naar wit als de gebruiker is uitgelogd.
Gebruik de
toggleLoginfunctie wanneer een gebruiker klikt op het icoon.
Uitgewerkte code
tsx
import {StyleSheet, View, Image, Text, Pressable} from 'react-native';
import {useRouter} from 'expo-router';
import {FontAwesome6} from '@expo/vector-icons';
import {ChatContext} from "../providers/ChatContext";
import {useEffect, useContext} from "react";
import logo from '../assets/logo.png';
export default function Header() {
const router = useRouter();
const {toggleLogin, getUser, isLoggedIn} = useContext(ChatContext);
useEffect(() => {
getUser();
}, [isLoggedIn])
let userDisplay = isLoggedIn ? <FontAwesome6 name="user-circle" size={30} color="#00bda5"/> :
<FontAwesome6 name="user-circle" size={30} color="#FFFFFF"/>;
return (
<View style={styles.header}>
<Pressable
onPress={() => {
router.push('/');
}}
>
<Image
source={logo}
style={styles.logoStyle}
/>
</Pressable>
<Text style={styles.menu}>
Web Expert
</Text>
<Text style={styles.menu}>
Chat
</Text>
<Text
style={styles.menu}
onPress={() => {
router.push('/register');
}}
>
Registreer
</Text>
<Text
style={styles.menu}
onPress={toggleLogin}
>
{userDisplay}
</Text>
</View>
);
};
const styles = StyleSheet.create({
header: {
alignItems: 'center',
backgroundColor: '#212121',
height: 120,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 5
},
logoStyle: {
height: 50,
width: 50,
resizeMode: 'stretch'
},
menu: {
color: '#00bda5',
fontSize: 16,
alignSelf: 'center',
paddingHorizontal: 5
}
});De layout template
Het laatste wat we moeten doen om onze context te kunnen gebruiken, is ervoor zorgen dat componenten deel uitmaken van de provider zodat alle variabelen beschikbaar zijn voor alle componenten.
Take The Wheel
- Importeer de ChatProvider van de ChatContext
- Gebruik de ChatProvider in de template en zorg dat alle componenten binnen dit component vallen.
Uitgewerkte code
tsx
import {Slot} from 'expo-router';
import {SafeAreaView} from 'react-native-safe-area-context';
import {ChatProvider} from "../providers/ChatContext";
import Header from '../components/Header';
import Footer from '../components/Footer';
export default function Layout() {
return (
<ChatProvider>
<SafeAreaView>
<Header/>
<Slot/>
<Footer/>
</SafeAreaView>
</ChatProvider>
);
};De Registratie pagina
Voordat gebruikers kunnen inloggen moeten ze zich eerst kunnen registreren. Maak daarom een registratie pagina op basis van de volgende opdracht.
Take The Wheel
Maak een bestand genaamd
register.tsxImplementeer een registratieformulier met de volgende velden:
- Gebruikersnaam invoerveld
- Wachtwoord invoerveld
- Wachtwoordbevestiging invoerveld
Voeg validatie toe voor:
- Controle op lege gebruikersnaam
- Controle op leeg wachtwoord
- Overeenkomst wachtwoordbevestiging
- Controle op bestaande gebruikersnaam
Implementeer navigatie met Expo Router:
- Navigeer terug naar de startpagina na succesvolle registratie
- Voorzie een annuleerknop om terug te keren naar de startpagina
Zorg ervoor dat gebruikers op deze pagina terecht komen wanneer ze in de Header klikken op de
Registreerknop.
Voor de stijl kan je het volgende gebruiken als basis:
tsx
const styles = StyleSheet.create({
container: {
height: boundedHeight,
...Platform.select({
android: {
paddingBottom: 360
},
ios: {
paddingBottom: 160
},
default: {
paddingBottom: 20
}
}),
},
formView: {
alignItems: 'center'
},
formTitle: {
...Platform.select({
android: {
fontSize: 20,
paddingVertical: 10
},
ios: {
fontSize: 20,
paddingVertical: 10
},
default: {
fontSize: 30,
paddingVertical: 20
}
})
},
formLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 10
},
ios: {
fontSize: 16,
paddingTop: 10
},
default: {
fontSize: 24,
paddingTop: 18
}
})
},
formInput: {
width: 250,
borderWidth: 1,
padding: 10,
...Platform.select({
android: {
fontSize: 16,
},
ios: {
fontSize: 16,
},
default: {
fontSize: 24,
width: 400
}
})
},
formButtonLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 12
},
ios: {
fontSize: 16,
paddingTop: 12
},
default: {
fontSize: 24,
paddingTop: 20
}
})
}
});Uitgewerkte Code
tsx
import {
StyleSheet,
Text,
View,
ScrollView,
TextInput,
TouchableOpacity,
Alert,
Dimensions,
Platform
} from 'react-native';
import {useState} from 'react';
import {useRouter} from 'expo-router';
import * as SecureStore from 'expo-secure-store';
const windowHeight = Dimensions.get('window').height;
export default function Register() {
const router = useRouter();
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const cancelRegistration = () => {
router.replace('/');
};
const registerUser = async () => {
if (userName.trim() === '') {
Alert.alert('Fout', 'Voer een gebruikersnaam in');
return;
}
if (password === '' || passwordConfirm === '') {
Alert.alert('Fout', 'Voer een wachtwoord en bevestiging in');
return;
}
if (password !== passwordConfirm) {
Alert.alert('Fout', 'Wachtwoorden komen niet overeen');
return;
}
const storedUser = await SecureStore.getItemAsync('userLoggedIn');
if (storedUser && storedUser === userName) {
Alert.alert('Fout', 'Gebruikersnaam bestaat al');
return;
}
await SecureStore.setItemAsync(userName, password);
Alert.alert('Succes', 'Account aangemaakt', [
{
text: 'OK',
onPress: () => router.replace('/')
}
]);
};
return (
<View style={styles.container}>
<ScrollView contentContainerStyle={styles.formView}>
<Text style={styles.formTitle}>Account Registratie</Text>
<Text style={styles.formLabel}>Gebruikersnaam</Text>
<TextInput
style={styles.formInput}
value={userName}
onChangeText={setUserName}
placeholder="Voer gebruikersnaam in"
/>
<Text style={styles.formLabel}>Wachtwoord</Text>
<TextInput
style={styles.formInput}
value={password}
onChangeText={setPassword}
placeholder="Voer wachtwoord in"
secureTextEntry
/>
<Text style={styles.formLabel}>Bevestig Wachtwoord</Text>
<TextInput
style={styles.formInput}
value={passwordConfirm}
onChangeText={setPasswordConfirm}
placeholder="Bevestig wachtwoord"
secureTextEntry
/>
<TouchableOpacity
onPress={registerUser}
>
<Text style={styles.formButtonLabel}>ACCOUNT REGISTREREN</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={cancelRegistration}
>
<Text style={styles.formButtonLabel}>ANNULEREN</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
height: windowHeight,
...Platform.select({
android: {
paddingBottom: 160
},
ios: {
paddingBottom: 160
},
default: {
paddingBottom: 20
}
}),
},
formView: {
alignItems: 'center'
},
formTitle: {
...Platform.select({
android: {
fontSize: 20,
paddingVertical: 10
},
ios: {
fontSize: 20,
paddingVertical: 10
},
default: {
fontSize: 30,
paddingVertical: 20
}
})
},
formLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 10
},
ios: {
fontSize: 16,
paddingTop: 10
},
default: {
fontSize: 24,
paddingTop: 18
}
})
},
formInput: {
width: 250,
borderWidth: 1,
padding: 10,
...Platform.select({
android: {
fontSize: 16,
},
ios: {
fontSize: 16,
},
default: {
fontSize: 24,
width: 400
}
})
},
formButtonLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 12
},
ios: {
fontSize: 16,
paddingTop: 12
},
default: {
fontSize: 24,
paddingTop: 20
}
})
}
});De Login pagina
Een gebruiker kan zich registreren met het registratieformulier. Het is nu ook de bedoeling dat hij zich kan aanmelden. Hiervoor bouwen we een formulier waarmee we kunnen nagaan of de ingevulde gegevens overeenkomen met de gegevens die opgeslagen zijn in de lokale opslag (via SecureStore). Op de login pagina voorzien we volgende functionaliteit:
- Er kan een gebruikersnaam en wachtwoord ingegeven worden via een formulier.
- De credentials worden vergeleken met de opgeslagen data.
- De state van de applicatie wordt geüpdatet wanneer een gebruiker succesvol is ingelogd.
- Navigatie opties zodat gebruikers het aanmelden kunnen annuleren of ervoor kunnen kiezen om zich aan te melden.
Take The Wheel
Maak een bestand genaamd
login.tsxImplementeer een loginformulier:
- Gebruik een View element met daarbinnen een ScrollView
- Voorzie binnen dit ScollView
- Een Text veld voor de titel van het formulier
- Een Text veld het label van het invulveld voor de gebruikersnaam
- Een TextInput veld voor de gebruikersnaam
- Een Text veld het label van het invulveld voor het wachtwoord
- Een TextInput veld voor het wachtwoord en zorg ervoor dat het wachtwoord niet zichtbaar blijft, maar vervangen wordt door "bolletjes" (zoals een password inputveld in HTML)
- Drie TouchableOpacity velden (waarbinnen telkens een Text veld wordt voorzien)
- Om de gebruiker in te loggen
- Om het aanmelden te annuleren
- Om naar de registratiepagina te navigeren
Zorg dat je aan de
setIsLoggedIndata van de applicatie kan door de context te initialiseren met deuseContexthook en geef de ChatContext mee als argument.Maak een state variabele aan voor de gebruikersnaam (string) en link dit aan het TextInput veld voor de gebruikersnaam.
Maak een state variabele aan voor het wachtwoord (string) en link dit aan het TextInput veld voor het wachtwoord.
Laad de router in.
Maak een functie cancelLogin waarmee de gebruiker naar de startpagina wordt geleid. Link deze functie aan de knop om het aanmelden te annuleren.
Maak een functie createAccount waarmee de gebruiker naar de registratiepagina wordt geleid. Link deze functie aan de juiste knop.
Maak een functie loginUser waarmee de gebruiker ingelogd wordt en link deze functie aan de juiste knop. Voorzie binnen deze functie het volgende:
- Wanneer geen gebruikersnaam werd ingevuld, toon je een gepaste Alert
- Wanneer geen wachtwoord werd ingevuld, toon je een gepaste Alert
- Wanneer beide gegevens wel zijn ingevuld:
- Ga je na of er al een gebruiker is ingelogd. Indien dat al het geval is, geef je een gepast Alert en leid je de gebruiker naar de startpagina.
- Indien er nog geen gebruiker is ingelogd, ga je na of het ingevulde wachtwoord overeenkomt met het opgeslagen wachtwoord voor de gebruiker. Is het wachtwoord fout, voorzie dan een gepaste Alert. Is het wel juist, zorg er dan voor
- dat je de gebruikersnaam toewijst aan userLoggedIn variabele van de SecureStore
- dat de loggedIn variabele de waarde true krijgt
- dat de gebruiker uiteindelijk naar de startpagina wordt geleid
- Indien er geen gebruiker werd gevonden, voorzie je ook een gepaste Alert.
Voor de stijl kan je de volgende code gebruiken als basis:
tsx
const styles = StyleSheet.create({
container: {
height: boundHeight,
...Platform.select({
android: {
paddingBottom: 160
},
ios: {
paddingBottom: 160
},
default: {
paddingBottom: 20
}
}),
},
formView: {
alignItems: 'center'
},
formTitle: {
...Platform.select({
android: {
fontSize: 20,
paddingVertical: 10
},
ios: {
fontSize: 20,
paddingVertical: 10
},
default: {
fontSize: 30,
paddingVertical: 20
}
})
},
formLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 10
},
ios: {
fontSize: 16,
paddingTop: 10
},
default: {
fontSize: 24,
paddingTop: 18
}
})
},
formInput: {
width: 250,
borderWidth: 1,
padding: 10,
...Platform.select({
android: {
fontSize: 16,
},
ios: {
fontSize: 16,
},
default: {
fontSize: 24,
width: 400
}
})
},
formButtonLabel: {
...Platform.select({
android: {
fontSize: 16,
paddingTop: 12
},
ios: {
fontSize: 16,
paddingTop: 12
},
default: {
fontSize: 24,
paddingTop: 20
}
})
}
})