๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

AWS

AWS Amplify๋ฅผ ์ด์šฉํ•œ React Native ์•ฑ ๊ฐœ๋ฐœ๊ธฐ (2) ์ธ์ฆ(authentication)

์ธ์ฆ ๊ธฐ๋Šฅ์„ ์œ„ํ•ด AWS Cognito๋ฅผ Amplify ์ƒ์—์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

๊ธฐ์กด AWS Cognito ๋ฆฌ์†Œ์Šค์™€ ํ†ตํ•ฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , Amplify Studio์—์„œ ์ƒˆ๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

Cognito๋Š” ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋น„์Šค์ด๋‹ค. Cognito์—์„œ ์‚ฌ์šฉ์ž ํ’€์„ ์ƒ์„ฑํ•˜๋ฉด ๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ์•ฑ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.


1. AWS - ์ธ์ฆ ๊ตฌ์„ฑ ๋ฐ ๋ฐฐํฌ

Amplify ์ŠคํŠœ๋””์˜ค์—์„œ ์ธ์ฆ ๊ธฐ๋Šฅ์„ ์„ค์ •ํ•˜๋ฉด CloudFormation์—์„œ Cognito๋ฅผ ์ฝ”๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑํ•œ๋‹ค.

 

๋กœ๊ทธ์ธ ๋ฐฉ์‹๊ณผ ํšŒ์›๊ฐ€์ž… ๋ฐฉ์‹์„ ์„ ํƒํ•œ ํ›„, Deployment๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

 

๋กœ๊ทธ์ธ ๋ฐฉ์‹์€ ์ด๋ฏธ์ง€์ฒ˜๋Ÿผ ์ „ํ™”๋ฒˆํ˜ธ, ์ด๋ฉ”์ผ, ์œ ์ €๋ช…, ๊ฐ„ํŽธ๋กœ๊ทธ์ธ(ํŽ˜์ด์Šค๋ถ, ๊ตฌ๊ธ€, ์•„๋งˆ์กด, ์• ํ”Œ) ์ค‘ ์„ ํƒํ•  ์ˆ˜ ์žˆ๊ณ , ํšŒ์›๊ฐ€์ž… ๋ฐฉ์‹์—์„œ๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ฐ›์„ ์ •๋ณด, ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•๋„, ๊ฐ€์ž… ์ธ์ฆ ๋ฐฉ์‹ ๋“ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

2. Local - Amplify pull

๋กœ์ปฌ์—์„œ pull ๋ช…๋ น์–ด๋ฅผ ์ˆ˜ํ–‰ํ•ด AWS ์ฝ˜์†”์—์„œ ์„ค์ •ํ•œ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋„๋ก ํ•œ๋‹ค.

amplify pull --appId (AppId) --envname (Status)

 

backend/auth/(์•ฑ์ด๋ฆ„)/build/parameters.json์— ์•ž์„œ ๊ตฌ์„ฑํ–ˆ๋˜ ์ •๋ณด๊ฐ€ ๊ทธ๋Œ€๋กœ ๋“ค์–ด๊ฐ€๊ณ , aws-exports.js ํŒŒ์ผ์—์„œ๋„ ์ธ์ฆ ๊ด€๋ จ ์ •๋ณด๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ๋‹ค.

 

3. Local  - UI ๊ตฌ์„ฑ

ํ™”๋ฉด ๊ตฌ์„ฑ์— ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•œ๋‹ค.

yarn add @aws-amplify/ui-react-native aws-amplify @aws-amplify/react-native react-native-safe-area-context @react-native-community/netinfo @react-native-async-storage/async-storage react-native-get-random-values
# ๋˜๋Š”
npm install @aws-amplify/ui-react-native aws-amplify @aws-amplify/react-native react-native-safe-area-context @react-native-community/netinfo @react-native-async-storage/async-storage react-native-get-random-values

 

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์•„์›ƒ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ๋” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์ด ๋‚˜์™€์žˆ๋‹ค.

import { signIn } from 'aws-amplify/auth';
import { signUp } from 'aws-amplify/auth';
import { confirmSignUp } from 'aws-amplify/auth';
import { signOut } from 'aws-amplify/auth';

 

 

Enable sign-up, sign-in, and sign-out - React Native - AWS Amplify Gen 1 Documentation

Learn how to use Amplify's sign-up, sign-in, and sign-out APIs. AWS Amplify Documentation

docs.amplify.aws

 

 

๋กœ๊ทธ์ธ ํ™”๋ฉด, ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด, ์ธ์ฆ ์ฝ”๋“œ ์ž…๋ ฅ ํ™”๋ฉด, ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•œ๋‹ค.

 

SignIn.js

๋”๋ณด๊ธฐ
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { signIn } from 'aws-amplify/auth';

import AppTextInput from '../components/AppTextInput';
import AppButton from '../components/AppButton';


function SignIn({ navigation, updateAuthState }) {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
  
    async function handleSignIn() {
      try {
        await signIn({ username, password });
        console.log('โœ… Success');
        updateAuthState('loggedIn');
      } catch (error) {
        console.log('โŒ Error signing in...', error);
      }
    }
  
    return (
      <SafeAreaView style={styles.safeAreaContainer}>
        <View style={styles.container}>
          <Text style={styles.title}>Sign in to your account</Text>
          <AppTextInput
            value={username}
            onChangeText={text => setUsername(text)}
            leftIcon="account"
            placeholder="Enter username"
            autoCapitalize="none"
            keyboardType="email-address"
            textContentType="emailAddress"
          />
          <AppTextInput
            value={password}
            onChangeText={text => setPassword(text)}
            leftIcon="lock"
            placeholder="Enter password"
            autoCapitalize="none"
            autoCorrect={false}
            secureTextEntry
            textContentType="password"
          />
          <AppButton title="Login" onPress={handleSignIn} />
          <View style={styles.footerButtonContainer}>
            <TouchableOpacity onPress={() => navigation.navigate('SignUp')}>
              <Text style={styles.forgotPasswordButtonText}>
                Dont have an account? Sign Up
              </Text>
            </TouchableOpacity>
          </View>
        </View>
      </SafeAreaView>
    );
};

const styles = StyleSheet.create({
    safeAreaContainer: {
      flex: 1,
      backgroundColor: 'white'
    },
    container: {
      flex: 1,
      alignItems: 'center'
    },
    title: {
      fontSize: 20,
      color: '#202020',
      fontWeight: '500',
      marginVertical: 15
    },
    footerButtonContainer: {
      marginVertical: 15,
      justifyContent: 'center',
      alignItems: 'center'
    },
    forgotPasswordButtonText: {
      color: 'tomato',
      fontSize: 18,
      fontWeight: '600'
    }
});


export default SignIn;

 

SignUp.js

๋”๋ณด๊ธฐ
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { signUp } from 'aws-amplify/auth';

import AppTextInput from '../components/AppTextInput';
import AppButton from '../components/AppButton';


function SignUp({ navigation }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [email, setEmail] = useState('');

  async function handleSignUp() {
    try {
      const { isSignUpComplete, userId, nextStep } = await signUp({ username, password, options: { userAttributes: { email } } });
      console.log('โœ… Sign-up Confirmed : ', userId);
      navigation.navigate('ConfirmSignUp');
    } catch (error) {
      console.log('โŒ Error signing up...', error);
    }
  }

  return (
    <SafeAreaView style={styles.safeAreaContainer}>
      <View style={styles.container}>
        <Text style={styles.title}>Create a new account</Text>
        <AppTextInput
          value={username}
          onChangeText={text => setUsername(text)}
          leftIcon="account"
          placeholder="Enter username"
          autoCapitalize="none"
          keyboardType="email-address"
          textContentType="emailAddress"
        />
        <AppTextInput
          value={password}
          onChangeText={text => setPassword(text)}
          leftIcon="lock"
          placeholder="Enter password"
          autoCapitalize="none"
          autoCorrect={false}
          secureTextEntry
          textContentType="password"
        />
        <AppTextInput
          value={email}
          onChangeText={text => setEmail(text)}
          leftIcon="email"
          placeholder="Enter email"
          autoCapitalize="none"
          keyboardType="email-address"
          textContentType="emailAddress"
        />
        <AppButton title="Sign Up" onPress={handleSignUp} />
        <View style={styles.footerButtonContainer}>
          <TouchableOpacity onPress={() => navigation.navigate('SignIn')}>
            <Text style={styles.forgotPasswordButtonText}>
              Already have an account? Sign In
            </Text>
          </TouchableOpacity>
        </View>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
    safeAreaContainer: {
      flex: 1,
      backgroundColor: 'white'
    },
    container: {
      flex: 1,
      alignItems: 'center'
    },
    title: {
      fontSize: 20,
      color: '#202020',
      fontWeight: '500',
      marginVertical: 15
    },
    footerButtonContainer: {
      marginVertical: 15,
      justifyContent: 'center',
      alignItems: 'center'
    },
    forgotPasswordButtonText: {
      color: 'tomato',
      fontSize: 18,
      fontWeight: '600'
    }
});


export default SignUp;

 

ConfirmSignUp.js

๋”๋ณด๊ธฐ
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { confirmSignUp } from 'aws-amplify/auth';

import AppTextInput from '../components/AppTextInput';
import AppButton from '../components/AppButton';


function ConfirmSignUp({ navigation }) {
  const [username, setUsername] = useState('');
  const [confirmationCode, setConfirmationCode] = useState('');

  async function handleSignUpConfirmation() {
    try {
      await confirmSignUp({username, confirmationCode});
      console.log('โœ… Code confirmed');
      navigation.navigate('SignIn');
    } catch (error) {
      console.log(
        'โŒ Error confirming...', error
      );
    }
  }

  return (
    <SafeAreaView style={styles.safeAreaContainer}>
      <View style={styles.container}>
        <Text style={styles.title}>Confirm Sign Up</Text>
        <AppTextInput
          value={username}
          onChangeText={text => setUsername(text)}
          leftIcon="account"
          placeholder="Enter username"
          autoCapitalize="none"
          keyboardType="email-address"
          textContentType="emailAddress"
        />
        <AppTextInput
          value={confirmationCode}
          onChangeText={text => setConfirmationCode(text)}
          leftIcon="numeric"
          placeholder="Enter verification code"
          keyboardType="numeric"
        />
        <AppButton title="Confirm Sign Up" onPress={handleSignUpConfirmation} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
    safeAreaContainer: {
      flex: 1,
      backgroundColor: 'white'
    },
    container: {
      flex: 1,
      alignItems: 'center'
    },
    title: {
      fontSize: 20,
      color: '#202020',
      fontWeight: '500',
      marginVertical: 15
    }
});


export default ConfirmSignUp;

 

Home.js

๋”๋ณด๊ธฐ
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { signOut } from 'aws-amplify/auth';


function Home({ updateAuthState }) {
  async function handleSignOut() {
    try {
      await signOut();
      updateAuthState('loggedOut');
    } catch (error) {
      console.log('Error signing out: ', error);
    }
  }

  return (
    <View style={styles.container}>
      <Text>๐Ÿ’™ + ๐Ÿ’›</Text>
      <Button title="Sign Out" color="tomato" onPress={handleSignOut} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    marginTop: 20
  }
});


export default Home;

 

์•ฑ์„ ์‹คํ–‰ํ•˜๋ฉด ์ธ์ฆ flow๊ฐ€ ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์—์„œ Amplify Studio์—์„œ ์„ ํƒํ–ˆ๋˜ ์ •๋ณด๋“ค์„ ์ž…๋ ฅํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๊ณ , confirm ํ™”๋ฉด์œผ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ์ž…๋ ฅํ•œ ๋ฉ”์ผ๋กœ ์ธ์ฆ์ฝ”๋“œ๊ฐ€ ๋ฐœ์†ก๋œ๋‹ค. ์ธ์ฆ์ฝ”๋“œ๋ฅผ ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด ํšŒ์›๊ฐ€์ž…์ด ์™„๋ฃŒ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์ž…ํ•œ ์ •๋ณด๋“ค๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ™”๋ฉด์ด ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋กœ๊ทธ์ธ์„ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ์˜ ํ™”๋ฉด

 

 

ํšŒ์›๊ฐ€์ž…์ด ์ •์ƒ์ ์œผ๋กœ ์™„๋ฃŒ๋˜๋ฉด Amplify Studio์™€ Cognito ์œ ์ €ํ’€์—์„œ๋„ ์‚ฌ์šฉ์ž๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.