Github OAuth의 경우 자료가 부족해 구현에 어려움이 있을 것 같아 작성합니다.

동작 방식

  1. App에서 Github 로그인 요청 (Web으로 연결)
  2. Web에서 로그인 성공 시 리다이렉션을 통해 앱을 실행
  3. 앱에선 intent.data 내부에 "code"라는 이름의 quaryParameter 값을 빼와서 이를 다시 accessToken 요청
  4. response 값으로 accessToken을 받을 수 있음

1. Github OAuth App 만들어주기

https://github.com/settings/developers

Application name: 앱 이름을 적어주시면 됩니다.
Homepage URL: 홈페이지가 있으면 홈페이지 url을 없으면 전 repository url을 적었습니다.
Authorization callback URL: DeepLink를 연결할 scheme과 host를 적으면 됩니다.
(ex: test://github-oauth)
(동작 방식의 리다이렉션을 통해 앱을 실행하기 위해 AndroidManifest에 작성할 값)

위의 값을 모두 작성한 이후, Register application 버튼을 클릭하면 됩니다.

여기서 필요한 값은 Client ID와 Client Secrets 값입니다.
Client secrets 값은 다시 들어가면 볼 수 없어 삭제 후 재발급해야하니 복사해놓는 것을 추천드립니다.

2. 웹을 통해 code 값 요청

Github의 accessToken 값을 받기 위해선, 먼저 code 값을 받아와야 합니다.
전 LoginActivity를 만든 후, 여기서 로그인 요청 및 요청 이후 해야할 일들을 하도록 구현하였습니다.

1. AndroidManifest.xml

<activity android:name=".view.login.LoginActivity"
	android:launchMode="singleTop"
	android:exported="true">
	<intent-filter>
		<action android:name="android.intent.action.VIEW" />

		<category android:name="android.intent.category.DEFAULT" />
		<category android:name="android.intent.category.BROWSABLE" />

		<data
			android:host="github-oauth"
			android:scheme="test"/>
	</intent-filter>
</activity>

launchMode를 singleTop으로 해준 이유

  • loginActivity에서 intent를 통해 web을 열어서 로그인 요청 이후, 로그인이 성공했을 때 Authorization callback URL에 적어 놓은 Url 뒤에 "code" 파라미터를 붙여서 링크를 실행하게 됩니다.
  • 그럼 intent-filter를 통해 host와 scheme이 같은 액티비티가 실행되게 되고, onNewIntent()를 통해 로직을 이어나가게 하기 위해 singleTop을 사용했습니다.

data 태그의 host와 scheme 값은 등록 시 넣었던 값들을 넣어주면 됩니다.

2. LoginActivity.kt

private fun login () {
	val githubAuthUrl = Uri.Builder().scheme("https").authority("github.com")
		.appendPath("login")
		.appendPath("oauth")
		.appendPath("authorize")
		.appendQueryParameter("client_id", BuildConfig.GITHUB_CLIENT_ID)
		.build()

        startActivity(Intent(Intent.ACTION_VIEW, githubAuthUrl).apply {
            addCategory(Intent.CATEGORY_BROWSABLE)
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        })
}

웹에서 로그인 요청을 하기 위해 해당 링크를 앱에서 실행하는 코드입니다.
appenQueryParameter 부분에 key 값은 "client_id", value 값은 저 같은 경우 BuildConfig로 처리를 했는데, OAuth App 만들면서 발급받은 Client ID 값을 넣으시면 됩니다.

이렇게 실행하면, 웹을 통해 깃헙 로그인을 하게 되고,
로그인 성공 시, Authorization callback URL를 통해 앱을 다시 실행하게 됩니다.
(저는 LoginActivity를 다시 실행할 수 있도록 하였습니다.)

LoginActivity의 경우 singleTop이니, 로그인 화면이 다시 실행될 때, onNewIntent() callback이 실행되게 되고, 여기서 "code" 값을 받아올 수 있습니다.

override fun onNewIntent(intent: Intent?) {
	super.onNewIntent(intent)
	val uri = intent?.data

	when (uri?.scheme) {
		"test" -> {
			val code = uri.getQueryParameter("code")
			code?.apply {
				viewModel.getAccessToken(code)
			}
		}
	}
}

여기서 아무거나 막 하면 안되니까, when을 통해 우리가 설정한 scheme 값인지 확인하고, code를 가져와 줍니다.

3. "code" 값을 통해 accessToken 요청하기

이렇게 받아온 code 값을 github에서 제공하는 rest api를 통해 accessToken을 받을 수 있습니다.
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps

1. Service

interface GithubService {
    @FormUrlEncoded
    @POST("https://github.com/login/oauth/access_token")
    @Headers("Accept: application/json")
    suspend fun getAccessToken(
        @Field("client_id") clientId: String,
        @Field("client_secret") clientSecret: String,
        @Field("code") code: String,
    ): Response<AccessTokenResponse>
}

2. AccessTokenResponse

data class AccessTokenResponse (
    @SerializedName("access_token")
    @Expose
    val accessToken: String,
    @Expose
    val scope: String,
    @SerializedName("token_type")
    @Expose
    val tokenType: String
)

3. Repository

class GithubRepository(
    private val githubService: GithubService,
    private val ioDispatcher: CoroutineDispatcher,
) {
    suspend fun getAccessToken(
        clientId: String,
        clientSecret: String,
        code: String
    ) : AccessTokenResponse? = withContext(ioDispatcher) {
        val response = githubService.getAccessToken(
            clientId = clientId,
            clientSecret = clientSecret,
            code = code
        )

        if (response.isSuccessful) {
            response.body()
        } else {
            null
        }
    }
}

4. ViewModel

fun getAccessToken(code: String) = viewModelScope.launch {
	loginStateLiveData.value = LoginState.Loading

	val response = githubRepository.getAccessToken(
		clientId = BuildConfig.GITHUB_CLIENT_ID,
		clientSecret = BuildConfig.GITHUB_CLIENT_SECRET,
		code = code
	)

	response?.let {
		signUp(it.accessToken)
	} ?: run {
		loginStateLiveData.value = LoginState.Error("액세스 토큰 발급 실패")
	}
}

여기서 OAuth App을 만들 때 발급받은 Client Secret 값을 사용하게 됩니다.
ClientId, ClientSecret, code를 넣어주고 api를 통해 요청하게 되면,
AccessTokenResponse를 통해 accessToken 값을 받아와 사용할 수 있습니다.


profile
화이팅!

0개의 댓글

Powered by GraphCDN, the GraphQL CDN