Merge pull request #912 from appwrite/feat-expand-auth-quickstart

Improve account overview page
This commit is contained in:
Vincent (Wen Yu) Ge
2024-05-01 09:59:13 -04:00
committed by GitHub
9 changed files with 649 additions and 81 deletions

View File

@@ -68,9 +68,10 @@ const languages = {
const platformAliases: Record<string, keyof typeof languages> = {
[Platform.ClientWeb]: 'js',
[Platform.ClientFlutter]: 'dart',
[Platform.ClientApple]: 'swift',
[Platform.ClientAndroidJava]: 'java',
[Platform.ClientAndroidKotlin]: 'kotlin',
[Platform.ClientApple]: 'swift',
[Platform.ClientReactNative]: 'js',
[Platform.ClientGraphql]: 'graphql',
[Platform.ClientRest]: 'http',
[Platform.ServerDart]: 'dart',

View File

@@ -29,6 +29,7 @@ export enum Platform {
ClientApple = 'client-apple',
ClientAndroidKotlin = 'client-android-kotlin',
ClientAndroidJava = 'client-android-java',
ClientReactNative = 'client-react-native',
ClientGraphql = 'client-graphql',
ClientRest = 'client-rest',
ServerNodeJs = 'server-nodejs',
@@ -51,6 +52,7 @@ export const platformMap: Record<Language | string, string> = {
[Platform.ClientWeb]: 'Web',
[Platform.ClientAndroidKotlin]: 'Android (Kotlin)',
[Platform.ClientAndroidJava]: 'Android (Java)',
[Platform.ClientReactNative]: 'React Native',
[Platform.ClientGraphql]: 'GraphQL',
[Platform.ClientRest]: 'REST',
[Platform.ServerDart]: 'Dart',

View File

@@ -8,9 +8,50 @@ back: /docs
Appwrite Authentication delivers more than just user sign up and log in.
Authentication makes it easy to build secure and robust authentication with support for many different authentication methods.
You can manage user accounts with user preferences, user labeling, or organizing users into teams.
Combined with a robust permissions system, Appwrite Authentication provides everything you need to authenticate and manage users.
{% arrow_link href="/docs/products/auth/quick-start" %}
Quick start
Add authentication to your app in 5 minutes
{% /arrow_link %}
# Authentication methods {% #auth-methods %}
Appwrite supports a variety of authentication methods to fit every app and every niche. Explore Appwrite's authentication flows.
{% cards %}
{% cards_item href="/docs/products/auth/email-password" title="Email and password" %}
Email and password login with just a few lines of code secured with state of the art Argon2 hasing.
{% /cards_item %}
{% cards_item href="/docs/products/auth/phone-sms" title="Phone (SMS)" %}
Log in users with out a password using their phone number and SMS verification.
{% /cards_item %}
{% cards_item href="/docs/products/auth/magic-url" title="Magic URL" %}
Passwordless login with a magic link sent to the user's email.
{% /cards_item %}
{% cards_item href="/docs/products/auth/email-otp" title="Email OTP" %}
Generate a time-based single-use password sent to the user's email.
{% /cards_item %}
{% cards_item href="/docs/products/auth/oauth2" title="OAuth 2" %}
Authenticate users with existing accounts from GitHub, Google, Facebook, and 30+ other providers.
{% /cards_item %}
{% cards_item href="/docs/products/auth/anonymous" title="Anonymous" %}
Create guest sessions for visitors and convert to full accounts when they're ready.
{% /cards_item %}
{% cards_item href="/docs/products/auth/jwt" title="JWT" %}
Deligate access for a user through passing JWT tokens.
{% /cards_item %}
{% cards_item href="/docs/products/auth/server-side-rendering" title="Server-side rendering (SSR)" %}
Authenticate users in server-side rendered applications.
{% /cards_item %}
{% cards_item href="/docs/products/auth/custom-token" title="Custom Token" %}
Implement custom authentication methods like biometric and passkey login by generating custom tokens.
{% /cards_item %}
{% cards_item href="/docs/products/auth/mfa" title="Multifactor authentication (MFA)" %}
Implementing MFA to add extra layers of security to your app.
{% /cards_item %}
{% /cards %}
# Flexible permissions {% #flexible-permissions %}
When users sign up using Appwrite, their identity is automatically attached to a robust permissions system.
Appwrite Authentication provides permissions for individual users and groups of users through [teams](/docs/products/auth/teams) and [labels](/docs/products/auth/labels).
# Built in preferences {% #built-in-preferences %}
Appwrite Authentication comes with built-in [preferences](/docs/products/auth/accounts#preferences) for users to manage their account settings.
Store notification settings, themes, and other user preferences to be shared across devices.

View File

@@ -4,10 +4,9 @@ title: Start with Authentication
description: Effortlessly add authentication to your apps - simple signup & login in just minutes with Appwrite Authentication
---
You can get up and running with Appwrite Authentication in minutes.
Adding signup and login is as simple as this.
# Sign up {% #sign-up %}
You can add basic email and password authentication to your app with just a few lines of code.
{% section #sign-up step=1 title="Signup" %}
You can use the Appwrite [Client SDKs](/docs/sdks#client) to create an account using email and password.
{% multicode %}
@@ -20,13 +19,11 @@ const client = new Client()
const account = new Account(client);
const promise = account.create('[USER_ID]', 'email@example.com', '');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});
const user await = account.create(
'ID.unique()',
'email@example.com',
'password'
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';
@@ -76,7 +73,6 @@ val user = account.create(
)
```
```graphql
mutation {
accountCreate(userId: "unique()", email: "email@example.com", password: "password") {
_id
@@ -85,15 +81,8 @@ mutation {
}
}
```
{% /multicode %}
# Login {% #login %}
After you've created your account, users can be logged in using the [Create Email Session](/docs/references/cloud/client-web/account#createEmailSession) method.
{% multicode %}
```client-web
import { Client, Account } from "appwrite";
```client-react-native
import { Client, Account, ID } from "appwrite";
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
@@ -101,24 +90,29 @@ const client = new Client()
const account = new Account(client);
const promise = account.createEmailSession('email@example.com', 'password');
const user await = account.create(
'ID.unique()',
'email@example.com',
'password'
);
```
{% /multicode %}
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});
{% /section %}
{% section #login step=2 title="Login" %}
After you've created your account, users can be logged in using the [Create Email Session](/docs/references/cloud/client-web/account#createEmailSession) method.
{% multicode %}
```client-web
const session = await account.createEmailSession(
email,
password
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';
final client = Client()
.setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<PROJECT_ID>'); // Your project ID
final account = Account(client);
final session = await account.createEmailSession(
email: 'email@example.com',
password: 'password'
@@ -126,14 +120,6 @@ final session = await account.createEmailSession(
```
```client-apple
import Appwrite
let client = Client()
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<PROJECT_ID>") // Your project ID
let account = Account(client)
let session = try await account.createEmailSession(
email: "email@example.com",
password: "password"
@@ -141,15 +127,6 @@ let session = try await account.createEmailSession(
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account
val client = Client()
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<PROJECT_ID>") // Your project ID
val account = Account(client)
val session = account.createEmailSession(
email = "email@example.com",
password = "password"
@@ -166,14 +143,561 @@ mutation {
}
}
```
```client-react-native
const session = await account.createEmailSession(
email,
password
);
```
{% /multicode %}
{% /section %}
# More ways to authenticate {% #more-ways-to-authenticate %}
You can signup and login a user with an account create through
[email password](/docs/products/auth/email-password),
[phone (SMS)](/docs/products/auth/phone-sms),
[Anonymous](/docs/products/auth/anonymous)
[magic URL](/docs/products/auth/magic-url), and
[OAuth 2](/docs/products/auth/oauth2)
authentication.
{% section #check-authentication-state step=3 title="Check authentication state" %}
After logging in, you can check the authentication state of the user.
Appwrite's SDKs are stateless, so you need to manage the session state in your app.
You can use the [Get Account](/docs/references/cloud/client-web/account#get) method to check if the user is logged in.
{% multicode %}
```client-web
try {
const user = await account.get();
// Logged in
} catch (err) {
// Not logged in
}
```
```client-flutter
try {
final user = await account.get();
// Logged in
} catch(e) {
// Not logged in
}
```
```client-apple
do {
let user = try account.get()
// Logged in
} catch {
// Not logged in
}
```
```client-android-kotlin
return try {
val user = account.get()
// Logged in
} catch (e: AppwriteException) {
// Not logged in
}
```
```graphql
query {
accountGet {
_id
_createdAt
_updatedAt
name
registration
status
labels
passwordUpdate
email
phone
emailVerification
phoneVerification
prefs {
data
}
accessedAt
}
}
```
```client-react-native
try {
const user = await account.get();
// Logged in
} catch (err) {
// Not logged in
}
```
{% /multicode %}
{% /section %}
{% section #auth-and-nav step=4 title="Navigation (Optional)" %}
A common pattern is to use route guards to redirect users to the login page if they are not authenticated.
You can check the authentication state on app launch and before entering a protected route by calling `get()`.
Route guard implementations are **opinionated** and depend on the platform and frame you are using.
Take a look at some example usages from different platforms as inspiration.
{% accordion %}
{% accordion_item title="Web frameworks" %}
Before routing to a page, you can check if the user is logged in and redirect them to the login page if they are not.
{% tabs %}
{% tabsitem #react-router title="React router" %}
You can use [React router loaders](https://reactrouter.com/en/main/route/loader) to check if the user is logged in before rendering a route.
```client-web
import * as React from "react";
import {
createBrowserRouter,
} from "react-router-dom";
import "./index.css";
import Login from "./Login";
import Protected from "./Protected";
import { account } from "./lib/appwrite";
import { redirect } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/protected",
element: <Protected />,
loader: async () => {
try{
// logged in? pass user to the route
const user = await account.get();
return { user };
}
catch {
// not logged in? redirect to login
throw redirect('/login')
}
}
},
{
path: "/login",
element: <Login />,
},
]);
export default router;
```
{% /tabsitem %}
{% tabsitem #vue-router title="Vue router" %}
You can use [Vue router](https://router.vuejs.org/) wiht a [Pinia store](https://pinia.vuejs.org/) to check if the user is logged in before rendering a route.
First, create a simple Pinia store to manage the authentication state.
```client-web
import { account, ID, type Models } from '@/lib/appwrite'
import type { register } from 'module';
import { defineStore } from 'pinia'
export const useAuthStore = defineStore({
id: 'auth',
state: () => ({
user: null as null | Models.User<Models.Preferences>,
}),
getters: {
isLoggedIn(): boolean {
return !!this.user;
},
},
actions: {
async init() {
try {
this.user = await account.get();
}
catch (error) {
this.user = null;
}
},
// ... other operations
},
})
```
Then, check the authentication state before routing to a protected route.
```client-web
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { useAuthStore } from './stores/auth'
const app = createApp(App)
app.use(createPinia())
const auth = useAuthStore();
auth.init().then(() => {
router.beforeEach((to, from, next) => {
// Not logged in?
if (to.name == 'protected' && auth.isLoggedIn == false) {
// Redirect to login if going to a protected route
next({ name: 'login' })
} else {
next()
}
})
app.use(router)
app.mount('#app')
})
```
{% /tabsitem %}
{% tabsitem #angular-router title="Angular router" %}
```client-web
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable, from, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { account } from './lib/appwrite';
@Injectable({
providedIn: 'root'
})
export class AuthGuardGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (route.routeConfig?.path === "protected") {
return this.checkLogin();
}
return of(true);
}
private checkLogin(): Observable<boolean | UrlTree> {
return from(account.get()).pipe(
map(() => true),
catchError(() => of(this.router.createUrlTree(['/login'])))
);
}
}
```
{% /tabsitem %}
{% tabsitem #svelte title="Svelte" %}
In the root level `+layout.svelte` file, you can check the authentication state before rendering a route.
```client-web
// src/routes/+layout.js
import { appwrite } from "$lib/appwrite";
// Turn off SSR globally, turning the project into a static site
export const ssr = false;
export const load = async () => {
try {
return {
account: await appwrite.account.get(),
};
} catch {
return {
account: null,
};
}
};
```
This will be accessible in the `load` function of each child route.
```client-web
// src/routes/protected/+page.js
import { redirect } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export async function load({ parent }) {
const { account } = await parent();
if (!account) {
throw redirect(303, '/login');
}
}
```
{% /tabsitem %}
{% /tabs %}
{% /accordion_item %}
{% accordion_item title="Mobile and native" %}
With mobile apps, you can apply similar logic to check the authentication state before displaying a screen or view.
{% tabs %}
{% tabsitem #flutter-go-router title="Flutter Go router" %}
This example uses the Flutter Go router as an example, but the same concepts apply to other routing libraries.
First, create a `ChangeNotifier` to manage the authentication state.
```client-flutter
import 'package:flutter/material.dart';
import 'package:appwrite/appwrite.dart' show Client, ID;
import 'package:appwrite/appwrite.dart' as Appwrite;
import 'package:appwrite/models.dart' show User;
class Account extends ChangeNotifier {
final Appwrite.Account _account;
User? _user;
User? get user => _user;
Account(Client client) : _account = Appwrite.Account(client);
Future<void> init() async {
try {
_user = await _account.get();
notifyListeners();
} catch(e) {
debugPrint(e.toString());
rethrow;
}
}
// ... other operations
}
```
You can then use this state to redirect users to the login page if they are not authenticated.
```client-flutter
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import './providers/account.dart';
import './pages/login.dart';
import './pages/protected.dart';
String Function(BuildContext context, GoRouterState state) redirect =
(BuildContext context, GoRouterState state) =>
context.read<Account>().user == null && state.matchedLocation != '/login'
? '/login'
: state.matchedLocation;
final router = GoRouter(
redirect: redirect,
initialLocation: '/login',
routes: [
GoRoute(
path: '/login',
pageBuilder: (context, state) => const MaterialPage(child: LoginPage()),
),
GoRoute(
path: '/protected',
pageBuilder: (context, state) => const MaterialPage(child: ProtectedPage()),
)
],
);
```
{% /tabsitem %}
{% tabsitem #apple title="Apple" %}
For Apple platforms, this example uses a `NavigationStack` but you can use similar concepts with other navigation methods.
Initialize Appwrite and create an `AppwriteService`.
```client-apple
import Foundation
import Appwrite
import AppwriteModels
import JSONCodable
class Appwrite {
var client: Client
var account: Account
var databases: Databases
let databaseId = "default"
let collectionId = "ideas-tracker"
public init() {
self.client = Client()
.setEndpoint("https://cloud.appwrite.io/v1")
.setProject("<YOUR_PROJECT_ID>")
self.account = Account(client)
}
public func getUser() async throws -> User<[String: AnyCodable]> {
let user = try await account.get()
// you can also store the user in a local store
return user
}
}
```
On launch, you can display a `SplashView` while you verify the authentication state.
```client-apple
import Foundation
import SwiftUI
struct SplashView: View {
@EnvironmentObject private var router: Router
@EnvironmentObject private var AppwriteService: AppwriteService
var body: some View {
NavigationStack(path: $router.routes) {
VStack {
Text("Example App")
.font(.largeTitle)
.fontWeight(.bold)
.padding()
}.task {
let user = await self.AppwriteService.getUser();
if !user {
router.pushReplacement(.login)
} else {
router.pushReplacement(.protected)
}
}
.navigationDestination(for: Route.self, destination: { $0 })
}
}
}
```
In your router, you can also check the authentication state before rendering a route.
```client-apple
final class Router: ObservableObject {
@Published var routes = [Route]()
func push(_ screen: Route) {
// Make sure you've already stored the user and auth state in a local store
if (screen == .protected && !isLoggedIn){
routes.append(.login)
}
routes.append(screen)
}
// ... other operations
}
```
{% /tabsitem %}
{% tabsitem #android title="Android" %}
Create some Appwrite Service, for example, `AppwriteService` to manage the authentication state.
You can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1).
```client-android-kotlin
//... imports
object Appwrite {
private const val ENDPOINT = "https://cloud.appwrite.io/v1"
private const val PROJECT_ID = "<YOUR_PROJECT_ID>"
private lateinit var client: Client
fun init(context: Context) {
client = Client(context)
.setEndpoint(ENDPOINT)
.setProject(PROJECT_ID)
}
}
```
Then, create an auth service to manage the authentication state.
```client-android-kotlin
//... imports
class AccountService(client: Client) {
private val account = Account(client)
suspend fun getLoggedIn(): User<Map<String, Any>>? {
return try {
account.get()
} catch (e: AppwriteException) {
null
}
}
// ... other operations
}
```
Wrap your routes in some view, for example, `AppContent`, to check the authentication state before rendering a route.
```client-android-kotlin
@Composable
private fun AppContent(accountService: AccountService) {
val user = remember { mutableStateOf<User<Map<String, Any>>?>(null) }
val screen = remember { mutableStateOf(Screen.Protected) }
LaunchedEffect(screen) {
user.value = accountService.getLoggedIn()
}
Scaffold(bottomBar = { AppBottomBar(screen) }) { padding ->
Column(modifier = Modifier.padding(padding)) {
when (screen.value) {
Screen.User -> LoginScreen(user, accountService)
else -> ProtectedScreen(user.value)
}
}
}
}
```
In the `MainActivity` class, initialize the Appwrite service and display the `AppContent` based on the authentication state.
```client-android-kotlin
// ...imports
In the `MainActivity` class, initialize the Appwrite service.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Appwrite.init(applicationContext)
setContent {
// Update this line 👇
AppContent(Appwrite.account)
}
}
}
```
{% /tabsitem %}
{% tabsitem #react-native title="React Native" %}
This example will use `@react-navigation/native` and `@react-navigation/native-stack` to manage the authentication state and redirect users to the login page if they are not authenticated.
You can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1).
Create a `UserContext` to manage the authentication state.
```client-react-native
import { StyleSheet, Text, View } from 'react-native';
import { UserProvider } from './contexts/UserContext';
import { Router } from './lib/Router';
export default function App() {
return (
<UserProvider>
<Router />
</UserProvider >
);
}
```
Then, consume the `UserContext` in your `Router` component to check the authentication state before rendering a route.
```client-react-native
import { NavigationContainer } from '@react-navigation/native';
import LoginScreen from '../views/Login';
import ProtectedSCreen from '../views/Protected';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useUser } from '../contexts/UserContext';
const Stack = createNativeStackNavigator();
export function Router() {
const user = useUser();
return (
<NavigationContainer>
<Stack.Navigator>
{user.current == null ? (
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ title: 'Login' }}
/>
) : (
<Stack.Screen
name="Protected"
component={ProtectedSCreen}
options={{ title: 'Protected' }}
/>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
```
{% /tabsitem %}
{% /tabs %}
{% /accordion_item %}
{% /accordion %}
{% /section %}

View File

@@ -88,7 +88,7 @@ Open `App.js` and add the following code to it, replace `<YOUR_PROJECT_ID>` with
This imports and initializes Appwrite and defines some basic authentication methods.
```ts
```client-react-native
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native';
import { Client, Account, ID } from 'react-native-appwrite';
@@ -137,7 +137,7 @@ With `Client` and `Account` service initialized, you can now use them to make yo
Add the following components to your `App.js` file to create a simple login form.
```ts
```client-react-native
<View style={styles.root}>
<Text>
{loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}
@@ -192,7 +192,7 @@ Add the following components to your `App.js` file to create a simple login form
You can also add some simple styling to your app by adding the following styles to your `App.js` file.
```ts
```client-react-native
const styles = StyleSheet.create({
root: {
marginTop: 40,

View File

@@ -12,7 +12,7 @@ You can use a context and a custom hook to manage our user's data.
Create a new file `contexts/UserContext.jsx` and add the following code to it.
```js
```client-react-native
import { ID } from "react-native-appwrite";
import { createContext, useContext, useEffect, useState } from "react";
import { account } from "../lib/appwrite";
@@ -76,7 +76,7 @@ For a better user experience, display toasts when the users perform an action, s
We can do this by creating a new file `lib/toast.js` and adding the following code to it.
```js
```client-react-native
import { ToastAndroid, Platform, AlertIOS } from 'react-native';
export function toast(msg) {
@@ -93,7 +93,7 @@ Create a new file `views/Login.jsx` and add the following code to it.
this page contains a basic form to allow the user to login or register.
Notice how this page consumes the `useUser` hook to access the user's data and perform login and register actions.
```js
```client-react-native
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import { useUser } from '../contexts/UserContext';

View File

@@ -12,7 +12,7 @@ Based on the user's login status, you'll redirect them to the login page or the
Create a new file `views/Home.jsx` and add the following stub code to it.
We'll update this page later to display the ideas posted by other users and allow the user to post their ideas.
```js
```client-react-native
import React from 'react';
import { View, Text } from 'react-native';
import { useUser } from '../contexts/UserContext';
@@ -37,7 +37,7 @@ To handle basic routing, you can use the `react-navigation` library.
This router also consumes the `UserContext` to determine if the user is logged in or not to redirect the user automatically.
Create a file `lib/Router.jsx` and add the following code to it.
```js
```client-react-native
import { NavigationContainer } from '@react-navigation/native';
import LoginScreen from '../views/Login';
import HomeScreen from '../views/Home';
@@ -72,7 +72,7 @@ export function Router() {
We'll display this router in the `App.js` file.
```js
```client-react-native
import { StyleSheet, Text, View } from 'react-native';
import { UserProvider } from './contexts/UserContext';

View File

@@ -45,7 +45,7 @@ Now that you have a collection to hold ideas, we can read and write to it from o
Like you did with the user data, we will create a React context to hold our ideas.
Create a new file `contexts/IdeasContext.jsx` and add the following code to it.
```js
```client-react-native
import { ID, Permission, Role, Query } from "react-native-appwrite";
import { createContext, useContext, useEffect, useState } from "react";
import { databases } from "../lib/appwrite";
@@ -108,7 +108,7 @@ This permission ensures that only the user who created the idea can modify it.
Remeber to add the `IdeasProvider` to your `App.js` file.
```js
```client-react-native
import { StyleSheet, Text, View } from 'react-native';
import { UserProvider } from './contexts/UserContext';

View File

@@ -13,7 +13,7 @@ that only the owner of the idea can remove it.
Overwrite the contents of `views/Home.jsx` with the following:
```js
```client-react-native
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, ScrollView } from 'react-native';
import { useUser } from '../contexts/UserContext';