Swift, OAuth가 무엇인지 알아봅니다

OAuthSwift, AeroGear OAuth2

Posted by MinJun Ju on Tuesday, September 25, 2018 Tags: Swift OAuth Raywenderlich   18 minute read

OAuth 2.0 with Swift Tutorial의 내용을 의역했습니다.


Contents

  • Getting Started
  • Explaining the Need for OAuth 2.0
  • The Authorization Dance
  • Step 0: Registration
  • Step 1: Authorization Code
  • Step 2: Exchange Code for Token
  • Step 3: Get Resources
  • Registering With your OAuth 2.0 Provider
  • Authenticating with AeroGear and External Browsers
  • Configuring the URL Scheme
  • OAuthSwift with Embedded Safari View
  • Configuring URL Handling
  • More About Tokens
    • Storing tokens
  • Refreshing and Revoking
  • Where To Go From Here?

이 OAuth 2.0 Swift 튜토리얼에서 두가지 오픈소스 라이브러를 사용하여 iOS앱에서 OAuth 2.0을 구현하는 방법을 배우게 됩니다.

Update note: 이 튜토리얼은 Swift 4로 업데이트 되었습니다.

OAuth 2.0과 다양한 제품군에 부딪혀 좋아하는 선호하는 소셜 네트워크(facebokk, twiiter 등) 또는 기업 OAuth 2.0 서버와 콘텐츠를 공유할 수 있는 앱을 구축하는 중일것입니다. 심지어 내부에서 무슨일이 일어나고 있는지 알지 못하는 경우에도 마찬가지 입니다. 하지만 iOS 앱에서 OAuth 2.0을 사용하여 서비스에 연결하는 방법을 알고 있나요?

이 튜토리얼에서, 익명으로 이름된 셀카 공유앱으로 작업하며, AeroGear OAuth2OAuthSwfit 오픈 소스 OAuth 2.0 라이브러리를 사용하여 구글 드라이브에서 셀카를 공유하는 방법을 배우게 됩니다.


Getting Started

여기에서 시작프로젝트를 다운받습니다. 시작 프로젝트는 CocoaPods를 사용하여 AeroGear 의존성을 가져오고, 생성한 pods, 워크스페이스 디렉토리 등 이 튜토리얼에 필요한 모든것을 포함하고 있습니다.

Xcode에서 Incognito.xcworkspace를 엽니다. 프로젝트는 single view Application 템플릿 기반이고, 하나의 뷰 컨트롤러(ViewController.swift)를 가지고 있는 단일한 스토리보드로 구성되어 있습니다. 모든 행동은 이미 ViewController.swift에서 처리되었습니다.

프로젝트를 빌드하고 실행하여 앱이 다음과같이 보이는지 봅니다.

이 앱을 사용하면 최상의 셀카를 골라 몇가지 엑세서시를 이미지에 추가할수 있습니다.

Note: 시뮬레이터에서 사진을 추가하고, Cmd + Shift + H를 사용하여 간단하게 홈 화면으로 가고 이미지를 시뮬레이터로 드레그엔 드랍 합니다.

앱에 누락된 부분은 두개의 서로 다른 OAuth 2.0 라이브러리를 사용하여 Google 드라이브에서 공유할수 있는 기능을 추가하는 것입니다.

불가능한 미션 같나요? 아닙니다. 처리하지 못하는건 아무것도 없습니다.

RFC6749 OAuth2 specification에 대한 이야기를 소개하는 대신, 이야기를 해드리겠습니다.


Explaining the Need for OAuth 2.0

월요일 아침에, 우리의 모바일 괴짜인 Bob은 다른 친숙한 괴짜인 Alice와 커피 머신 앞에서 부딪혔습니다. Bob은 많은 문서들고 바쁜것 처럼 보입니다. 그의 사장님은 익명 앱(the Incognito app)에 대한 OAuth 2.0 사양(OAuth 2.0 specification)대해 자세히 알아보기를 원합니다.

두명의 개발자를 커피룸 두면 그들은 괴상한 것들에 대해 담소를 나눕니다. Bob이 Alice에게 묻습니다

"..OAuth 2.0으로 해결하려는 문제는 무엇인가요?"

한편으로는 Twitter API와 같은 API형식의 서비스를 사용하여 follower 목록과 트윗을 얻는데 사용할수 있습니다. 그 API들은 기밀 데이터를 처리하고, 로그인과 패스워드 암호로 부터 보호합니다.

다른편에는, 이러한 서비스를 사용하는 앱들이 있습니다. 이러한 앱들은 데이터에 접근 합니다. 하지만 자격증명(credentials)으로 그들을 의 모든 앱을 신뢰하나요? 어쩌면 그렇지 않을수 있습니다.

그렇다면 여기에서 위임된 접근 개념(concept of delegated access)를 가져옵니다. OAuth2는 access token으로 알려진 보안 객체를 통해 암호 공유 없이 그들의 웹 리소스에 접근을 허락합니다. 접근 토큰으로 암호가 메인 서비스 내부에 안전하게 유지되어 접근 토큰(access token)으로 암호를 획득하는게 불가능합니다. 앱이 서비스에 연결하길 원한다면, 그 자체의 접근 토큰(access token)을 얻어야만 합니다. 그런 다음, 접근 토큰은 해당 앱에 대한 접근 권한을 취소 하려는 경우 접근 토큰(access token)을 취소할수 있습니다.

OAuth 2.0은 다음 4명의 배우(가상 인물)들과 일합니다.

  • authorization server: 인증(authentication) 과 인가(authorization)에 대한 권한이 있고, access token을 제공합니다.
  • resource server: 유요한 토큰이 제공되면 리소스들을 제공하는 역할을 담당합니다.
  • resource owner: 데이터의 주인- Incognito의 최종 사용자
  • client: Incognito mobile app

OAuth 2.0 사양(OAuth 2.0 specification)은 이러한 배우들 간에 상호 작용을 grant flows로 설명합니다.

이 스펙은 친숙한 두가로 그룹화되는 네가지 서로다른 허가 흐름(grant flows)로 자세하게 설명합니다.

  • 3-legged flows: 최종 사용자는 이러한 경우 허가(permission)를 부여(grant)해야합니다. 암시적 승인(implicit grant)은 토큰 보안을 유지할수 브라우저 기반 앱용입니다. 접근 토큰(access token) 및 선택적 재생 토큰(optionally a refresh token)을 생성하는 인가 코드 자격(The authorization code grant)은 토큰 보안을 유지할수 있는 클라이언트를 위한 것입니다. 이러한 클라이언트에는 iOS의 키 체인과 같이 안전하게 토큰을 저장할수 있는 모바일 앱이 포함됩니다.
  • 2-legged flows: 자격 증명(the credentials)이 앱에게 주어집니다. 주요한 차이점은 리소스 소유자가 자격증명을 클라이언트에게 직접 제공(input)한다는 것입니다. 실제로 이것을 보는 예는 개발자로서 많은 API(예: Parse)에 접근하고 앱에 키를 입력하는 경우입니다.

기존 구글 드라이브 계정을 사용하여 Incognito 앱에 셀카를 업로드 합니다. 이것은 3-legged 인가 코드 부여(3-leggend authorization code grant)를 구현하기 위한 좋 사례입니다.


The Authorization Dance

오픈소스 라이브러리를 사용하면 OAuth 2.0 프로토콜의 대부분의 세부사항이 숨겨지지만 기본 내부동작을 알면 올바르게 구성(configuration)을 수행할수 있습니다.

여기의 단계는 authorization code grant dance 와 연관된 단계 입니다.


Step 0: Registration

애플리케이션은 접근하길 원하는 서비스와 함께 등록하는것이 필요합니다. 당신의 경우에는, Incognito를 위해서, Google Drive로 등록해야합니다. 걱정하지마세요. 다음 섹션에서는 어떻게 수행하는지 설명합니다.


Step 1: Authorization Code

이 dance는 Inocognito가 다음을 포함하는 제3의 서비스로 인가 코드(an authorization code)를 위한 요청을 보낼때 시작(begins)합니다.

  • client ID: 서비스 등록중 제공됩니다. 앱이 서비스에게 이야기 한다고 정의합니다
  • redirect URL: 유저의 자격(their credentials)이 서비스로 입력되고, 권한을 부여한 이후에 유저가 되돌아와야하는 곳입니다.
  • scope: 앱이 가져야하는 자격의 수준이 얼만큼인지 서비스에게 이야기하는데 사용됩니다.

그런 다음 앱이 웹 브라우절 전환합니다. 사용자가 로그인하면, 구글 인가 서버(Google authorization server)는 승인 페이지를 보여줍니다: “Incognito는 당신의 사진첩에 접근하길 원합니다: 허용/반대”. 최종 사용자가 허용을 클릭하면 서버는 리디렉션 URI를 사용하여 Incognito 앱으로 되돌아가고 앱에게 authorization cdoe를 전송합니다.


Step 2: Exchange Code for Token

인가 코드(the authorization code)는 단지 임시적 입니다. 그러므로 OAuth 2.0 라이브러리는 그 임시적인 코드를 적절한 접근 토큰(access token), 선택적으로 재생 토큰(refresh token)을 위해 교환해야 합니다.


Step 3: Get Resources

접근 토큰을 사용하여, Incognito는 서버에 있는 보호된 자원에 접근할수 있습니다. 즉, 최종 사용자가 권한을 부여한 자원(resources) 입니다. 업로드 처리는 무료입니다.


Registering With your OAuth 2.0 Provider

구글 계정을 가지고 있지 않다면, 생성하세요.

https://console.developer.google.com 해당 주소로 가면 구글에서 인증하라는 메시지가 표시됩니다.

다음으로 Drive API를 가능하게 합니다.

왼쪽매뉴에서 라이브러리(Library)를 클릭하고 Google Drive API를 검색하여 선택합니다. 다음 화면에서 Enable을 클릭합니다.

이제 앱에서 드라이브 계정으로 접근하기 위한 새로운 인증서(new credentialis)를 생성해야 합니다.

왼쪽 메뉴에서 Credentials 을 클릭하고 파란색 Create Credentials drop down메뉴에서, OAuth client ID를 선택하세요.

그리고 난 이후 Configure consent screen을 클릭하고 나타난 스크린 화면에서, 다음 정보를 작성합니다.

  • Email address: Select your email addres
  • product name: Incognito
  • homepage URL: http://www.raywenderlich.com

저장(save)을 클릭하고 Client ID 화면으로 돌아 옵니다. iOS를 선택하고 com.raywenderlich.Incognito 앱의 번들 ID로서 등록합니다.

인증 서버는 redirect URL로서 위에 입력된 bundle id를 사용할것입니다.

마지막으로 Create를 클릭합니다. 클라이언트 ID가 표시된 팝업을 확인하세요. 나타나면 확인(OK)를 클릭하세요. 나중에 필요한 중요한 매개변수는 Client ID 입니다.

이제 Google에 등록 했으므로, 첫번째 OAuth 2.0 라이브러리(외부 브라우저와 함께인 AeroGear)를 사용하여 OAuth 2.0 구현을 시작할 준비가 되었습니다.


Authenticating with AeroGear and External Browsers

ViewController.swift 를 열고 파일 최상단에 다음을 추가합니다.

import AeroGearHttp
import AeroGearOAuth2

ViewController 클레스 에 다음코드를 인스턴스 변수로 추가합니다.

private let http = Http(baseURL: "https://www.googleapis.com")

http인스턴스를 사용하여 AeroGearHttp라이브러리에서 HTTP 요청을 수행합니다.

ViewController.swift에서 비어 있는share(_:) 메소드를 찾고 다음 코드를 추가합니다.

//1
let googleConfig = GoogleConfig(
  clientId: "YOUR_GOOGLE_CLIENT_ID",                           
  scopes:["https://www.googleapis.com/auth/drive"])            
    
//2
let gdModule = AccountManager.addGoogleAccount(config: googleConfig)
//3
http.authzModule = gdModule                               
//4
let multipartData = MultiPartData(data: snapshot(),       
  name: "image",
  filename: "incognito_photo",
  mimeType: "image/jpg")
let multipartArray =  ["file": multipartData]
//5    
http.request(method: .post, path: "/upload/drive/v2/files",  parameters: multipartArray) {
  (response, error) in
  if (error != nil) {
    self.presentAlert("Error", message: error!.localizedDescription)
  } else {
    self.presentAlert("Success", message: "Successfully uploaded!")
  }
}

해당 블로그에서는 API 주소가 /upload/drive/v2/files 이지만, 이때 설정이 잘못됫는지 잘 되지 않아서 /upload/drive/v3/files를 사용했습니다.

여기는 위의 매소드에서 무슨일이 일어나는지에 대한 설명입니다.

  1. 구성(configuration)을 생성합니다. 올바른 인증 구성을 사용하려면 위에 있는YOUR_GOOGLE_CLIENT_ID을 Google 콘솔의 클라이언트 ID로 바꾸어야 합니다. 초기화할때 승인된 요청 범위(scope of the grant request)도 정의 합니다. Incognito의 경우에, Drive API에 접근하는것이 필요합니다.
  2. 그런 다음 다목적의 AccountManager 매소드를 통해 OAuth2 모듈을 인스턴스화 합니다.
  3. 다음으로 HTTP 객체로 OAuth2 모듈을 주입하고, 이 객체는 HTTP 객체를 인가 모듈(the authorization module)에 연결합니다.
  4. 그리고 난 이후, 서버로 보내길 원하는 정보를 캡슐화하는 multi-part data object 를 생성합니다.
  5. 마지막으로, 사진을 업로드 하는 간단한 HTTP 호출을 사용합니다. 이 라이브러리는 OAuth2 모듈이 HTTP로 연결되어 있는지 검사하고 적절한 호출을 만듭니다. 이로 인해 다음 결과중 하나가 발생합니다.
    • 접근 토근이 존재하지 않으면, 인가 코드 부여(the authorization code grant)를 시작합니다.
    • 필요하다면 접근 토근을 새로 고칩니다(refresh)
    • 모든 토큰이 사용가능하다면, 간단하게 POST 호출을 시작합니다.

Note: AeroGear OAuth2 사용 방법에 대한 자세한 내용은 AeroGear의 온라인 문서API참조를 확인하거나 Pods 색션에서 소스 코드를 검색하세요.

앱을 빌드하고 실행합니다. 이미지를 선택하고 겹치는 이미지들을 추가한후, shard버튼을 살짝 누르세요. 메시지가 표시되면 Google 자격증명을 입력하세요. 이전에 로그인한적이 있다면, 자격은(credentials)은 캐시되어질 것입니다. 승인 페이지로 되돌아갑니다. 수락.. 수락을 누릅니다.

벰!- 사파리가 페이지를 열수없다는 에러 메시지를 받습니다.

수락을 탭하고, Google OAuth 사이트가 com.raywenderlich.Incognito://[some url]로 되돌아갑니다. 따라서 앱에서 이 URL scheme를 열도록 설정해야합니다.

Note: Safari는 시뮬레이터의 쿠키에 저장하므로 인증을 다시 요구하지 않습니다. 시뮬레이터의 이러한 쿠키를 지우려면 Hardware/Erase All Content and Settings 로 이동하세요.


Configuring the URL Scheme

유저가 Incognito로 되돌아 오는것을 허용 하려면, 사용자화된 URL scheme를 앱과 연결 해야합니다.

Xcode의 Incognito/Supporting Files로 이동하여 Info.plist를 찾습니다. 우 클릭후 Open As/Source Code를 클릭합니다.

plist 바닥에 </dict> 테크 직전에 다음을 추가합니다

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>com.raywenderlich.Incognito</string>
        </array>
    </dict>
</array>	

이 scheme는 URL의 첫번째 부분 입니다. 예를들어 웹페이지에서, scheme는 일반적으로 http 또는 https 를 사용합니다. iOS앱들은 com.raywenderlich.Incognito://doStuff 같은 그 자체의 사용자화된 URL scheme를 지정할수 있습니다. 중요한점은 사용자 기기에 설치된 모든 앱 중에서 고유한 사용자화 scheme를 선택하는 것입니다.

OAuth 2.0 댄스에서는 맞춤 URL scheme를 사용하여 요청을 보낸 애플리케이션으로 다시 돌아옵니다. URL과 마찬가지로 사용자화된 스킴은 매개변수를 가질수 있습니다. 이 경우에 인가 코드(the authorization code)는 코드 매개변수(code parameter)에 포함됩니다. OAuth 2.0라이브러리는 URL에서 인가 코드(authorization code)를 추출하여 접근 토근(access token)과 교환하여 다음 요청에 전달합니다.

사용자화된 URL 스킴을 통해서 앱이 실행 되었을때 앱이 응답하기 위해 Incognito의 AppDelegate 클레스 의 메소드를 구현하는게 필요합니다.

AppDelegate.swift를 열고 다음 코드를 import 구문 이후에 추가합니다.

import AeroGearOAuth2

다음으로 application(_:open:options)에 다음을 추가합니다.

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

  let notification = Notification(name: Notification.Name(AGAppLaunchedWithURLNotification),
                                  object:nil,
                                  userInfo:[UIApplicationLaunchOptionsKey.url:url])
  NotificationCenter.default.post(notification)
  return true
}

이 메소드는 단순하게 앱을 여는데 사용된 URL을 포함하는 Notification을 생성합니다. AeroGearOAuth2 라이브러리는 이 알람에 대한것을 수신하고 위에서 호출한 POST메소드의 completionHandler를 호출합니다.

프로젝트를 다시 만들고 실행하세요. 멋진 셀카를 가지고 차려입고, 공유 버튼을 클릭하여 자신을 인증하고 다음을 보세요.

이 색션의 완성된 Incognito프로젝트를 다운로드 할수 있습니다.

OAuth 2.0 인증 단계에서 컨텍스트를 외부 브라우저로 전환하는 것은 다소 어렵습니다. 보다 간소화된 접근 방식이 있어야 합니다.


Using Embedded Safari View

내장된 사파리 웹뷰는 더 사용자 친화적인 환경을 제공합니다. 사파리 애플리케이션으로 전환하는 대신 SFSafariViewController을 사용하여 이 작업을 수행할수 있습니다. 보안관점에서 볼때 앱의 코드가 로그인 양식과 제공자 사이에 있으므로 보안 수준이 낮습니다. 당신의 애플리케이션은 자바스크립트를 사용하여 사용자 자격증명에 접근할수 있습니다. 그러나 이것은 그러나 이것은 최종 사용자가 앱이 안전하고 신뢰할수 있는 경우 받아들일수 있는 옵션입니다.

OAuthSwift 라이브러리를 사용하여 share 매소드를 다시 살펴 보겠습니다. 이번에는 OAuth2.0 과 Safari view사용 하여 구현합니다.


OAuthSwift with Embedded Safari View

기존 Xcode 워크스페이스를 닫고, 새로운 이 프로젝트 다운로드 하고 난후 Incognito.xcworkspace 파일을 엽니다.

프로젝트를 빌드하고 실행한 후에, 이것들은 매우 친숙해 보여야합니다.

이전과 마찬가지로, 먼저 프로젝트에 포함된 OAuthSwfit 라이브러리를 가져와야합니다.

ViewController.swift를 열고, 파일의 최상단 import이후에 다음을 추가하세요.

ViewController.swiftshare() 메소드에 다음 코드를 추가합니다.

//1
let oauthswift = OAuth2Swift(
  consumerKey:    "YOUR_GOOGLE_DRIVE_CLIENT_ID",         
  consumerSecret: "",		// No secret required
  authorizeUrl:   "https://accounts.google.com/o/oauth2/auth",
  accessTokenUrl: "https://accounts.google.com/o/oauth2/token",
  responseType:   "code"
)

oauthswift.allowMissingStateCheck = true
//2
oauthswift.authorizeURLHandler = SafariURLHandler(viewController: self, oauthSwift: oauthswift)

guard let rwURL = URL(string: "com.raywenderlich.Incognito:/oauth2Callback") else { return }

//3
oauthswift.authorize(withCallbackURL: rwURL, scope: "https://www.googleapis.com/auth/drive", state: "", success: { 
  (credential, response, parameters) in
  oauthswift.client.postImage("https://www.googleapis.com/upload/drive/v2/files", 
    parameters: parameters, 
    image: self.snapshot(), 
    success: { 
      //4
      (response) in
      if let _ = try? JSONSerialization.jsonObject(with: response.data, options: []) {
        self.presentAlert("Success", message: "Successfully uploaded!")
      }
    }, 
    failure: { 
      (error) in
      self.presentAlert("Error", message: error.localizedDescription)
    })
}, failure: { (error) in
  self.presentAlert("Error", message: error.localizedDescription)
})

위의 코드에서 무슨일이 일어난것인지 설명합니다.

  1. 먼저, OAuth dance를 처리하는 OAuth2Swfit를 생성합니다. 구글 콘솔에서 client idYOUR_GOOGLE_CLIENT_ID를 변경하는걸 잊지마세요.
  2. 그 후 SFSafariViewController를 자동으로 표시(displaying), 해제(dismissing) 처리할 SafariURLHandlerauthorizeURLHandler를 초기화 합니다.
  3. 그런 다음, oAuthSwift 인스턴스를 통해서 인가(authorization)을 요청합니다. The scope 매개변수는 Drive API로 접근 요청을 나타냅니다.
  4. 권한이 부여되면, 이미지를 업로드할수 있습니다.

Configuring URL Handling

이전 프로젝트와 같이, 이 Incognito의 버전은 사용자화된 URL 스킴을 허용하도록 설정되어 있습니다. 사용자화 URL을 처리하는 코드를 구현하기만 하면 됩니다.

AppDelegate.swift를 열고 다음을 가져옵니다.

import OAuthSwift

그 다음 application(_:open:options)을 다음과같이 작성합니다.

func application(_ app: UIApplication,
                 open url: URL,
                 options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
  
  OAuthSwift.handle(url: url)
  return true
}

AeroGearOAuth2와는 달리 OAuthSwift는 클래스 메소드를 사용하여 반환된 URL의 구문을 처리합니다. 그러나 handle(_) 매소드를 검사하면, AeroGearOauth2에서 수행한것처럼 알림(Notification)을 보냅니다.

프로젝트를 빌드하고 실행하세요. 인증 양식(authentication form)이 나타나면 Safari앱에 표시되지 않으며 앱 전환이 발생하지 않습니다. 또한 기본적으로 앱에 웹 쿠키가 저장되지 않으므로 앱을 실행할때마다 인증 양식이 제공됩니다.

Google에서 인증할때 SFSafariViewController를 사용하면 더욱 간결해 보입니다:)

여기에서 최종 OAuthSwift 프로젝트를 다운할수 있습니다.


More About Tokens

보지못한 한가지는 OAuth 2.0 dance의 부분으로서 받는 귀중한 접근 토큰과 재생 토큰을 저장하는 방법입니다. 어디에 저장합니까? 만료된 토큰을 어떻게 새로 고치나요? 취득한 권한을 만료 시킬수 있나요?

Storing tokens

그들을 저장하는 최고의 방법은 Keychain..!

이것은 OAuth2Session(AeroGear)이 채택한 기본 전략입니다.

키 체인에 대해 더 알고싶다면, 이 주제에 대한 tutorial을 읽는것이 좋습니다.

Refreshing and Revoking

접근 토큰(acess token)을 고치려면, 단순하게 접근 토큰 끝점(endpoint)를 호출을 만들고, 매개변수로 새로고침 토큰을 전달하기만 하면됩니다.

예를 들어, AeroGear는 라이브러리에 토큰이 유효한지 여부를 결정합니다.

OAuth 2.0은 토큰 취소를 위한 다른 설명 또는 사양을(specification)을 정의합니다. 토큰을 개별적으 취소하거나, 모두 취소할수 있습니다. 대부분의 공급자는 접근 및 새로고침 토큰을 동시에 취소합니다.


Where To Go From Here?

OAuth 2.0을 구현 한 두개의 오픈소스 라이브러리를 다루어보았습니다. OAuth 2.0이 어떻게 작동하는지 더 자세히 알았으면 합니다.

이제 OAuth 2.0사양인 RFC6749를 읽을 준비가 디었습니다. 이것은 문서의 야수..이지만 적어도 OAuth의 기초와 앱과의 관계를 이해합니다.

앱에서 위에서 사용한 두개의 라이브러리중 하나를 사용하길 바랍니다. 일단 당신이 좋아한느 오픈 소스 OAuth 2.0 라이브러리를 선택했다면, 기여하는것이 필수적입니다. 버그가 발견되면 문제를 보고하세요. 고치는걸 아는 분이라면 pull request를 제안하세요.