안녕하세요 오늘은 애플 로그인에 대해 글을 써볼려고 합니다. (파베도 써요 ㅎ)
본론으로 들어가기전 Nonce에 대해 설명을 짧게 하도록 하겠습니다.
한번만 사용되는 문자열과 정수열의 난수를 말합니다
통상적으로 암호화 통신에 사용이 되고 한번만 사용하기 때문에 짧은 시간내에 여러번의 공격
Replay Attack과 CSRF을 예방할 수 있습니다.
클래스 안에
fileprivate var currentNonce: String?
위와 같이 클래스 내에 선언 해주세요.
func startSignInApple()이 메서드는 apple 로그인 flow를 시작하는 핵심적인 부분 입니다.
이 메서드 안에는 randomNonceString(length: ) , sha256(_: )
두가지 함수가 있습니다.
이 메서드는 보안 목적으로 사용하는 Nonce를 무작위로 생성합니다
SecRandomCopyBytes: 시스템의 보안 난수 생성기를 사용해 랜덤한 바이트 배열을 생성합니다.
이 메서드는 문자열을 sha256으로 해시화 합니다
ASAuthorizationControllerDelegate 프로토콜을 채택해 Apple 로그인이 성공했을 때, 또는 실패했을 때 처리할 수 있습니다.
didCompleteWithAuthorization(): 로그인이 성공하면 호출됩니다. Apple로부터 받은 identityToken을 이용해 Firebase와 연동해 인증합니다.currentNonce: 로그인 요청 시 생성한 nonce가 유효한지 확인합니다.identityToken: Apple에서 발급한 토큰으로 사용자 인증을 진행합니다.OAuthProvider.appleCredential: Firebase와 연동할 수 있는 인증 정보(credential)를 생성합니다.Auth.auth().signIn: Firebase에 이 인증 정보로 로그인 요청을 보냅니다.didCompleteWithError(): 로그인이 실패했을 때 호출되며, 에러를 출력합니다private extension LoginViewController {
func startSignInApple() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [ .fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests() }
@available(iOS 13, *)
func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
var randomBytes = [UInt8](repeating: 0, count: length)
let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
)
}
let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
let nonce = randomBytes.map { byte in
// 필요한 경우 세트에서 무작위로 캐릭터를 선택하여 랩을 감음
charset[Int(byte) % charset.count]
}
return String(nonce)
}
func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
}
extension LoginViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDcredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: 로그인 콜백이 수신되었지만 로그인 요청이 전송되지 않았습니다.")
}
guard let appleIDToken = appleIDcredential.identityToken else {
print("식별토큰을 가져올 수 없습니다.")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("데이터에서 토큰 문자열을 직렬화 할 수 없음: \(appleIDToken.debugDescription)")
return
}
// 사용자의 전체 이름을 포함한 Firebase 자격 증명을 초기화합니다.
let credential = OAuthProvider.appleCredential(withIDToken: idTokenString, rawNonce: nonce, fullName: appleIDcredential.fullName)
// Firebase에 로그인하기.
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
print("Apple Signin Error: \(error.localizedDescription)")
return
}
//로그인에 성공했을 시 실행할 메서드
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
// 에러 핸들링
print("Sign in with Apple errored: \(error)")
}
}
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
// Apple 로그인 인증 창 띄우기
return self.view.window ?? UIWindow()
}
}