[Flutter] Firebase를 활용한 애플 로그인 / Apple Sign-in With Firebase

S_Soo100·2022년 12월 8일
2

flutter

목록 보기
3/19
post-thumbnail

1. 구현하고자 하는 기능

1) 앱 내에서 apple 계정으로 회원가입 하는 기능

  • 소셜 로그인 기능을 붙이는 경우, apple의 App Store에서는 무조건 apple로그인 기능이 있어야 한다.
  • 이번 프로젝트에서는 ‘카카오톡’만 사용해서 로그인을 유도하려고 했으나, 애플의 정책으로 인해서 아이폰으로 앱을 실행할때만 애플 로그인 기능을 활성화 하기로 결정하였다.
      

2) 사용하는 기술 및 라이브러리

(1) 기술

  • Flutter (프로젝트 버젼은 3.3.3 을 사용하였다.)

(2) 라이브러리

(3) 기타 필요항목

  • 애플 개발자 계정
    ⇒ 개인 프로젝트를 진행 할 때는 이부분이 걸림돌일 수 있다.

2. 로그인 흐름 파악하기

1) 파이어베이스는 어떻게 로그인 하나요? 이메일로 로그인 하는 경우

  • 디자인 패턴은 MVVM을 사용했다.
  • 여러개로 쪼개져서 복잡해 보일 수 있지만 윗쪽에 있는 → 방향 화살표가 로그인을 요청 하는 부분, 아랫 쪽에 있는 ← 방향 화살표가 로그인 결과를 받는 부분이다.
  • view(화면)에서 viewModel의 createUser를 호출하면, 로직을 담당하고 있는 AuthRepository가 Firebase에게 Email과 password를 넘겨주며 이대로 계정을 만들어 달라고 한다.
  • 성공하면 Firebase 유저 타입의 객체를 return 받으며, 그 정보를 viewModel을 통해 다시 화면에서 볼 수 있다.

2) 여기에 소셜로그인 API를 추가하면 어떻게 되나요?

  • 디자인 패턴은 MVVM을 사용했다.
  • Firebase에 직접 email과 password를 전달하는 로그인 방식과 다르게, 애플 등 소셜로그인은 그 소셜로그인 api를 사용한다.
  • 애플을 기준으로 보면, appleSignIn()을 사용해서 애플 로그인(혹은 회원가입)창을 호출하고, 내 애플 계정으로 로그인 한다.
  • 그러면 애플 사용자의 accessToken, IdToken, nonce등이 오는데, 이를 다시 Firebase로 보내서 Firebase유저를 생성 혹은 받아온다.
  • 마지막으로 그 Firebase유저의 id값을 자체 서버에 넣어서(FireStore도 가능) 유저 데이터를 갖고오면 끝!

3. 애플 개발자 계정 세팅

1) 애플 개발자 페이지로 가서 로그인 한 후, 계정 설정(accout)으로 들어간다.
여기서 식별자와 Key, Service id를 등록해야 한다.

1️⃣  식별자 등록하기

  • *Certificates, Identifiers & Profiles 메뉴 중, 식별자(Identifiers)를 설정해야 한다.
    빨간색으로 표시한 “식별자(영문)”으로 들어가서, 식별자를 추가해준다.

  • App IDs를 선택하고 Continue 하자

  • App 유형을 선택하고 Continue

  • Description, Bundle ID를 기재한다. 번들 아이디는 모르겠다면 xcode의 러너로 들어가서 bundle identifier로 써두자
    그리고 아래 기능(capabilities)으로 가서 Sign In with Apple을 체크한다. 그후 Continue

2️⃣  Key 생성하기

  • 식별자(Identifiers)가 끝났으니 key 설정으로 들어간다. Create a Key 를 클릭.
  • 정보를 입력하고 “Sign in with Apple”을 활성화(enable)한다.
    그 후 우측의 Configure를 누른 다음 방금 설정한 identifier를 연결해주면 Continue가 가능하다.
  • 완료하면 키 파일 다운로드가 가능하다. 잘 보관해두자.

3️⃣  Service Id 생성하기

  • 다시 1번에서 했던 identifier생성으로 간다. 하지만 이번엔 App IDs가 아니라 Service IDs를 선택한다.
  • identifier는 아까 생성한 identifier의 역순으로 넣어주자.

  • 완료하면 아래 같이 Service IDs로 필터링이 된 목록이 나온다.
    방금 만든 서비스 아이디를 눌러서 설정을 해주자.

  • Sign in with Apple을 체크하고, Configure를 누르자.
  • 웹 서버 도메인과 콜백 주소를 입력해주면 된다.
    없는 경우, 임시로라도 넣어주자.

4. Firebase Console 설정하기

1) Firebase프로젝트 콘솔에서 빌드/Authentication 기능으로 접근,
Apple Sign-in method를 활성화 시킨다.
윗 단계에서 만든 서비스 ID를 넣어주면 된다.

2) Firebase프로젝트 콘솔에서 iOS앱을 추가하고, 구성 파일(GoogleService-Info.plist)을 우리 프로젝트의 ios폴더에 넣어준다. 번들 ID를 잘 맞춰주자.


5. X-CODE 설정하기

1) Targets → Runner → Signing & Capabilities → +Capabilities → Sign in with Apple. 이 기능을 활성화 해주면 된다.


6. 앱 내에 기능 구현하기

  1. 위에서 기재한 라이브러리를 모두 프로젝트에 설치한다.

    • firebase_auth는 모든 파이어베이스 로그인을,
      apple_sign_in은 애플 소셜 로그인 기능을 제공하는데,
      crypto는 애플 로그인시 임의 nonce 생성에 필요한 기능을 제공해서 함께 사용한다.
  2. noncer생성 및 해시값 변환 함수를 만든다.

    
    import 'package:firebase_auth/firebase_auth.dart';
    import 'package:sign_in_with_apple/sign_in_with_apple.dart';
    import 'package:crypto/crypto.dart';
    
    class FirebaseRepository {
    
    	String generateNonce([int length = 32]) {
    	  final charset =
    	      '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
    	  final random = Random.secure();
    	  return List.generate(length, (_) => 
    		charset[random.nextInt(charset.length)]).join();
    	}
    	
    	String sha256ofString(String input) {
    	  final bytes = utf8.encode(input);
    	  final digest = sha256.convert(bytes);
    	  return digest.toString();
    	}
    }
        
    
   3. 애플에 로그인하는 함수를 만든다.
        ```

        class FirebaseRepository {
        	_fireAuthInstance = FirebaseAuth.instance;
        
        	Future<UserCredential> signInWithApple() async {
        	  final rawNonce = generateNonce();
        	  final nonce = sha256ofString(rawNonce);
        		
        		//앱에서 애플 로그인 창을 호출하고, apple계정의 credential을 가져온다.
        	  final appleCredential = await SignInWithApple.getAppleIDCredential(
        	    scopes: [
        	      AppleIDAuthorizationScopes.email,
        	      AppleIDAuthorizationScopes.fullName,
        	    ],
        	    nonce: nonce,
        	  );
        	
        		//그 credential을 넣어서 OAuth를 생성
        	  final oauthCredential = OAuthProvider("apple.com").credential(
        	    idToken: appleCredential.identityToken,
        	    rawNonce: rawNonce,
        	  );
        		
        		//OAuth를 넣어서 firebase유저 생성
        		return await _fireAuthInstance.signInWithCredential(oauthCredential);
        	}
        }
        
  1. Sign in 버튼 만들기
        RoundedRectangleButtonWidget(
          buttonText: "애플로 시작하기",
          buttonColor: Colors.black,
          textColor: Colors.white,
          onPressed: () {
            signInWithApple();
          },
        ),
  1. Stream연결을 통해 Firestore에 유저 생성하기(선택)
    • Firebase Auth에서는 Stream으로 User를 구독해서 변화될 때 마다 알려주는 기능이 있다.
    • 이를 각자 사용하는 UserModel클래스의 스트림으로 변환해서, 유저가 바뀔 때 마다
      UserModel을 자동으로 바꿔줄 수 있다.
    • 이번에는 Firestore에 유저 모델을 저장한다고 가정하고 함수를 구성하였다.
        class FirebaseRepository {
        	_fireAuthInstance = FirebaseAuth.instance;
        	_firestoreInstance = FirebaseFirestore.instance;
        
        	Stream<UserModel?> getUserStream() {
        		//userChanges()는 User타입의 객체를 Stream으로 갖고오는 Firebase제공 함수이다.
        		//Firebase와 연결된 User가 변경될 때 마다 transform()을 실행한다.
        		//userModel은 제공해주는 타입이 아니라, 직접 만들어야 한다.
        		//model클래스 생성에 대해서는 freezed에 대해서 설명할때 또 다루겠다.
        	  return _fireAuthInstance.userChanges().transform(
        	      StreamTransformer<User?, UserModel?>.fromHandlers(
        	          handleData: (user, sink) async {
        	
        			//user타입을 갖고오는데 실패했으면 stream에 아무것도 추가하지 않는다.
        	    if (user == null) {
        	      sink.add(null);
        	      return;
        	    }
        	
        			var userCollection = _firestoreInstance.collection("user");
        			Map<String, dynamic> userDoc = {};
        	
        			try {
        	      //try catch로 유저 있어요?를 물어본다. 유저가 있다면 데이터를 긁어와주고 끝
        	      var snapshot = await userCollection.doc(user.uid).get();
        	      userDoc = snapshot.data() as Map<String, dynamic>;
        	    } catch (e) {
        				//유저가 없으면 만들어주자
        				var addedUser = await userCollection.doc(user.uid).set({
        					//나의 UserModel에 있는 param들을 넣는다.
        	        'uid': user.uid,
        	        'createdAt': DateTime.now(),
        	        'email': user.email,
        	        'nickName': '',
        	        'isTermChecked': false,
        					'age': 0,
        	      });
        	      var snapshot = await userCollection.doc(user.uid).get();
        	      userDoc = snapshot.data() as Map<String, dynamic>;
        	    }
        	    return sink.add(UserModel.fromJson(userDoc));
        	  }));
        	}
        
        }
    ```
  1. 이제 viewModel에서 Stream을 받자
        StreamSubscription<UserModel?>? sub;
        UserModel? _userModel;
        
        void _bindUser(Stream<UserModel?> userStream) {
        	sub?.cancel();
        	sub = userStream.listen((user){
        		_userModel = user;
        	})
        }
profile
플러터, 리액트

1개의 댓글

comment-user-thumbnail
2024년 10월 17일

잘읽었습니다 감사합니다!

답글 달기