이미 이 블로그에도 적용돼있지만, 나는 Google 계정을 이용한 로그인을 좋아한다.
그런데 지금 블로그는 조금 구 버전의 문서인 Sign in with Google for Web 문서를 보고 만들어져서, OAuth2로 마이그레이션 할 겸 만든 것들을 공유하고자 한다.
OAuth2 프로토콜에 대한 설명은 생략하고, 이 문서를 보면 자바스크립트를 이용한 OAuth2.0 액세스 토큰을 발급 받는 방법을 알 수 있다.
방법은 간단하다.
아래 URL로 리디렉트 해주면 끝이다.
https://accounts.google.com/o/oauth2/v2/auth?
client_id=client_id&
redirect_uri=https%3A//oauth2.example.com/code&
response_type=token&
scope=profile email&
state=state_parameter_passthrough_value&
include_granted_scopes=true&
enable_granular_consent=true&
login_hint=example@gmail.com&
prompt=consent
각 파라미터에 대해 간단하게 알아보자.
이름 | 필수여부 | 설명 |
---|---|---|
client_id | true |
사용자 인증화면에서 발급 받은 클라이언트 아이디 |
redirect_uri | true |
OAuth2.0 토큰 획득 후 리디렉트 할 Uri. 사용자 인증페이지에서 설정 가능 |
response_type | true |
자바스크립트 앱에선 token 값으로 고정 |
scope | true |
앱이 사용자를 대신해 액세스 할 수 있는 리소스 배열. 공백으로 구분 |
state | false |
권장 필드. 상태값을 Google OAuth서버에 제공한 후 리디렉스 Uri에 포함되도록 하여 보안을 강화하기 위함 |
include_granted_scopes | false |
boolean 값을 사용하며, 앱이 점진적 권한 부여 기능을 사용할 지 말지 설정. 기본 값은 false |
enable_granular_consent | false |
기본 값은 false . true 로 설정할 경우 2019년 이전에 생성된 OAuth 클라이언트 ID에 대해 더 세분화 된 Google 계정 권한이 비활성화 됨. |
prompt | false |
none , consent , select_account 중 하나 사용. 자세한 값은 문서 참고 |
이제 이 내용들을 반복 사용하기 위해 Angular 서비스를 만들어줬다.
@Injectable({
providedIn: 'root',
})
export class OauthService {
redirectToGetGoogleAccessToken({
clientId,
redirectUri,
scope = ['email', 'profile'],
prompt,
state,
includeGrantedScopes,
enableGranularConsent,
loginHint,
}: GoogleRedirectOptions): void {
const url = new URL('https://accounts.google.com/o/oauth2/v2/auth');
url.searchParams.set('client_id', clientId);
url.searchParams.set('redirect_uri', redirectUri);
url.searchParams.set('response_type', 'token');
url.searchParams.set('scope', scope.join(' '));
if (prompt !== undefined) {
url.searchParams.set('prompt', prompt);
}
if (includeGrantedScopes !== undefined) {
url.searchParams.set('include_granted_scopes', String(includeGrantedScopes));
}
if (enableGranularConsent !== undefined) {
url.searchParams.set('enable_granular_consent', String(enableGranularConsent));
}
if (loginHint !== undefined) {
url.searchParams.set('login_hint', loginHint);
}
if (state !== undefined) {
url.searchParams.set('state', state);
}
location.href = url.toString();
}
}
나는 구글 계정을 이용한 로그인 기능만 사용할 것이기 때문에 scope
의 기본 값을 email
과 profile
로 지정해줬다.
액세스 토큰을 이용해 사용자 정보를 조회할 때 email
과 profile
은 각각 다음 정보들을 포함한다.
email
: 구글 이메일email_verified
: 이메일 인증 여부name
: given_name
과 family_name
을 합친 이름given_name
: 구글 계정에 등록된 이름family_name
: 구글 계정에 등록된 성picture
: 구글 계정 아바타 URL이렇게 하면 이제 OauthService.redirectToGetGoogleAccessToken()
메서드를 호출했을 때 다음과 같이 구글 계정을 선택하는 화면으로 이동한다.
위 화면에서 계정을 선택하면 이제 redirect_uri
로 설정한 URL로 이동하게 되는데, 이 때 URL Fragment에 결과값들이 포함되어 아래와 같은 형태로 전달된다.
{your_redirect_url}#access_token={access_token}&token_type=Bearer&expires_in=3599&scope=email%20profile%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/userinfo.email%20openid&authuser=0&prompt=consent
이렇게 전달되는 값을 이용하기 위해, redirect_uri
로 지정된 페이지에서 다음과 같이 처리해줬다.
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-oauth-page',
standalone: true,
imports: [],
templateUrl: './oauth-page.component.html',
styleUrl: './oauth-page.component.scss',
host: {
class: 'flex-col-stretch',
},
})
export class OauthPageComponent {
constructor(private readonly _activatedRoute: ActivatedRoute) {
this._activatedRoute.fragment.pipe(takeUntilDestroyed()).subscribe((fragment) => {
if (fragment) {
const urlSearchParams = new URLSearchParams(fragment);
// Use parsed data.
}
});
}
}
ActivatedRoute
를 이용해 fragment
를 읽어오고, URLSearchParams
로 fragment
를 사용하기 쉽게 변경해줬다.
하지만 이 상태만으로는 사용자 계정 정보를 알 수 없기 때문에 활용하기에 제한이 있다.
따라서 액세스 토큰과 아래 API를 이용해 사용자 정보를 불러온다.
https://www.googleapis.com/oauth2/v3/userinfo?access_token={your_access_token}
위 API를 호출하면 아래와 같은 결과를 얻을 수 있다.
{
"sub": "",
"name": ",
"given_name": "",
"family_name": "",
"picture": "",
"email": "",
"email_verified": true
}
개인 정보들은 다 가렸지만, 실제로는 구글 계정 소유자의 정보가 표시될 거다.
이제 이 정보들을 이용해 로그인 처리를 하든, 새 계정을 만들든 하면 된다.
여담인데 Google Authentication, OAuth 관련 API 문서를 가끔 쓸 때마다 문서 위치를 찾는게 너무 헷갈린다.
이번에 이렇게 스니펫 화를 해놨으니 한 동안 문서 찾아 헤맬 일은 없겠지만 문서 위치 찾느라 조금 고생했다.
심지어 사용자 정보를 불러오는 https://www.googleapis.com/oauth2/v3/userinfo
API 관련 문서는 찾지도 못하고 다른 사람들이 쓴 글을 통해서 알게됐다.
구글 시스템이 워낙 방대하다보니 이해는 한다만 조금만 더 잘 정리돼있었으면 하는 아쉬움이 있다.