사용자가 앱을 승인한 후에는 인증 코드와 함께 도메인으로 다시 리디렉션됩니다. 서버 쪽에서는 Instagram 앱의 사용자 인증 정보를 사용하여 액세스 토큰을 얻기 위해 인증 코드를 교환할 수 있습니다. 인증 코드를 교환하는 프로세스에서 Instagram은 사용자 ID도 반환합니다. LinkedIn 등의 다른 OAuth 2.0 공급자를 이용하는 경우에는 가끔 추가적인 요청이 필요하기도 합니다.
서버에서 Instagram 사용자 정보를 가져온 후 Firebase
맞춤 인증 토큰을 생성합니다. 사용자는 이 토큰을 통해
signInWithCustomToken 메서드를 사용하여 웹 앱에서 Firebase에 로그인할 수 있습니다.
클라이언트에서 Firebase 프로필을 업데이트할 수 있도록 표시 이름, 사진 URL 등, Instagram에서 가져온 사용자 프로필 정보도 전달해보겠습니다.
(참고: Instagram은 사용자의 이메일을 제공하지 않으므로 이메일 정보가 없는 Firebase 계정을 가지게 될 텐데, 그래도 괜찮습니다.) 작업을 마쳤으면 팝업을 닫습니다. 그러면 이제 사용자가 Instagram 계정에서 가져온 프로필 데이터로 Firebase에 로그인됩니다.
서버에서 OAuth 2.0 프로토콜의 세부 정보를 숨기는 데 도움이 되는
simple-oauth2 패키지를 사용하겠습니다. 이 패키지를 설정하려면 Instagram 클라이언트 ID와 비밀번호, 그리고 Instagram의 OAuth 2.0 토큰 및 승인 엔드포인트와 같은 몇 가지 값을 제공해야 합니다. 다음은 Instagram에 사용해야 하는 값입니다.
// Instagram OAuth 2 setup
const credentials = {
client: {
id: YOUR_INSTAGRAM_CLIENT_ID, // Change this!
secret: YOUR_INSTAGRAM_CLIENT_SECRET, // Change this!
},
auth: {
tokenHost: 'https://api.instagram.com',
tokenPath: '/oauth/access_token'
}
};
const oauth2 = require('simple-oauth2').create(credentials);
Instagram 인증 흐름 시작
서버에서 사용자를 Instagram의 동의 화면으로 리디렉션하는 URL 핸들러를 추가하세요. 이 작업의 일부로, 사용자가 Instagram 인증 흐름을 거친 후 다시 리디렉션되는 경로인
리디렉션 URI를 입력해야 합니다. 이 경우에는
/instagram-callback
을 콜백 핸들러 경로로 사용하겠습니다.
app.get('/redirect', (req, res) => {
// Generate a random state verification cookie.
const state = req.cookies.state || crypto.randomBytes(20).toString('hex');
// Allow unsecure cookies on localhost.
const secureCookie = req.get('host').indexOf('localhost:') !== 0;
res.cookie('state', state.toString(), {maxAge: 3600000, secure: secureCookie, httpOnly: true});
const redirectUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`,
scope: 'basic',
state: state
});
res.redirect(redirectUri);
});
또한,
세션 고정 공격을 피하기 위해 OAuth 요청의 상태 매개변수에 임의의 문자열을 전달하고 이를 HTTP 쿠키로도 저장합니다. 이를 통해 반환되는 상태 매개변수를 쿠키에 저장된 매개변수와 비교하고 흐름이 앱에서 발생했는지 확인할 수 있습니다.
클라이언트에서 다음과 같이 팝업을 트리거하는 버튼을 추가하세요.
function onSignInButtonClick() {
// Open the Auth flow in a popup.
window.open('/redirect', 'firebaseAuth', 'height=315,width=400');
};
사용자가 로그인 버튼을 클릭하면 팝업이 열리면서 Instagram 동의 화면으로 리디렉션됩니다.
사용자가 승인하면
code
URL 쿼리 매개변수에 전달된 승인 코드와 앞서 전달한
state
값과 함께
/instagram-callback
URL 핸들러로 다시 리디렉션됩니다.
액세스 토큰을 얻기 위한 승인 코드 교환
사용자가 콜백 URL로 다시 리디렉션되면 다음을 수행하세요.
- 상태 쿠키가 상태 URL 쿼리 매개변수와 동일한지 확인하세요.
- 인증 코드를 교환하여 액세스 토큰을 얻고 Instagram에서 사용자 ID를 검색하세요.
app.get('/instagram-callback',(req, res) => {
// Check that we received a State Cookie.
if (!req.cookies.state) {
res.status(400).send('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
// Check the State Cookie is equal to the state parameter.
} else if (req.cookies.state !== req.query.state) {
res.status(400).send('State validation failed');
}
// Exchange the auth code for an access token.
oauth2.authorizationCode.getToken({
code: req.query.code,
redirect_uri: `${req.protocol}://${req.get('host')}/instagram-callback`
}).then(results => {
// We have an Instagram access token and the user identity now.
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// ...
});
});
이제 이 구현의 OAuth 2.0 관련 부분을 마쳤으므로, 다음으로 할 일은 대부분 Firebase와 관련된 일입니다.
다음으로, Firebase 맞춤 인증 토큰을 생성하고, 맞춤 인증 토큰으로 사용자를 로그인시키고 Firebase 사용자 프로필을 업데이트할 HTML 페이지를 제공합니다(이에 대해서는 나중에 자세히 설명함).
app.get('/instagram-callback', (req, res) => {
// ...
}).then(results => {
// We have an Instagram access token and the user identity now.
const accessToken = results.access_token;
const instagramUserID = results.user.id;
const profilePic = results.user.profile_picture;
const userName = results.user.full_name;
// Create a Firebase custom auth token.
const firebaseToken = createFirebaseToken(instagramUserID);
// Serve an HTML page that signs the user in and updates the user profile.
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
맞춤 인증 토큰 생성
Firebase 맞춤 인증 토큰을 생성하려면
서비스 계정 사용자 인증 정보를 사용하여 Firebase를 설정해야 합니다. 그러면 이런 토큰을 만드는 데 필요한 관리 권한이 부여됩니다. 서비스 계정 사용자 인증 정보 파일을
service-account.json
으로 저장해야 합니다.
const firebase = require('firebase');
const serviceAccount = require('./service-account.json');
firebase.initializeApp({
serviceAccount: serviceAccount
});
맞춤 인증 토큰은 간단히 만들 수 있습니다. Instagram의 사용자 ID를 기반으로 사용자의 uid를 선택하기만 하면 됩니다.
function createFirebaseToken(instagramID) {
// The uid we'll assign to the user.
const uid = `instagram:${instagramID}`;
// Create the custom token.
return firebase.auth().createCustomToken(uid);
}
(참고: 서비스 계정 사용자 인증 정보는 안전하게 지켜야 하므로, 맞춤 토큰은 항상 서버 쪽에서 생성해야 합니다.)
맞춤 토큰을 생성했으면 Firebase에 로그인하는 클라이언트에 이를 전달할 수 있습니다.
맞춤 토큰을 사용하여 로그인
이 시점에서 서버는 팝업 창 내에서 실행되는 HTML 페이지를 제공하며 다음 작업을 수행합니다.
- 나중에 Instagram API에 액세스해야 하는 경우 Instagram 액세스 토큰을 실시간 데이터베이스에 저장합니다. (참고: 사용자만 읽을 수 있도록 하는 보안 규칙을 사용해야 합니다.)
- Firebase 사용자의 이름과 프로필 사진을 업데이트합니다.
- 사용자를 로그인시키고 팝업을 닫습니다.
기본 Firebase 앱을 사용하는 대신 임시
Firebase 앱 인스턴스를 사용하여 프로필을 업데이트하는 것도 하나의 묘책입니다. 그러면 사용자의 프로필이 업데이트되기 전에 기본 페이지에서 인증 리스너가 트리거되지 않도록 차단됩니다.
app.get('/instagram-callback', (req, res) => {
// ...
// Serve an HTML page that signs the user in and updates the user profile.
res.send(
signInFirebaseTemplate(firebaseToken, userName, profilePic, accessToken)));
});
});
function signInFirebaseTemplate(token, displayName, photoURL, instagramAccessToken) {
return `
<script src="https://www.gstatic.com/firebasejs/3.4.0/firebase.js"></script>
<script src="promise.min.js"></script><!-- Promise Polyfill for older browsers -->
<script>
var token = '${token}';
var config = {
apiKey: MY_FIREBASE_API_KEY, // Change this!
database URL: MY_DATABASE_URL // Change this!
};
// We sign in via a temporary Firebase app to update the profile.
var tempApp = firebase.initializeApp(config, '_temp_');
tempApp.auth().signInWithCustomToken(token).then(function(user) {
// Saving the Instagram API access token in the Realtime Database.
const tasks = [tempApp.database().ref('/instagramAccessToken/' + user.uid)
.set('${instagramAccessToken}')];
// Updating the displayname and photoURL if needed.
if ('${displayName}' !== user.displayName || '${photoURL}' !== user.photoURL) {
tasks.push(user.updateProfile({displayName: '${displayName}', photoURL: '${photoURL}'}));
}
// Wait for completion of above tasks.
return Promise.all(tasks).then(function() {
// Delete temporary Firebase app and sign in the default Firebase app, then close the popup.
var defaultApp = firebase.initializeApp(config);
Promise.all([
defaultApp.auth().signInWithCustomToken(token),
tempApp.delete()]).then(function() {
window.close(); // We’re done! Closing the popup.
});
});
});
</script>`;
}
사용자가 팝업에서 기본 Firebase 앱에 로그인된 후 인증 상태 리스너가 기본 페이지에서 트리거됩니다. Firebase에서는 인증 상태가 여러 탭 간에 공유된다는 점을 기억하세요. 사용자 프로필 정보를 표시하고, 실시간 데이터베이스, Firebase 저장소 등을 사용할 수 있습니다.
직접 시험해 보세요!
저희는 개발자 여러분이 시험해 볼 수 있는 데모 앱을
https://instagram-auth.appspot.com/에 만들어 두었습니다.
이 샘플은 오픈소스입니다. Github(
https://github.com/firebase/custom-auth-samples)에서 관련 리소스를 자유롭게 살펴보시기 바랍니다.
Android와 iOS는 어떠냐고요?
지금까지 이 글에서 보여드린 코드는 웹 앱에서 작동하는 코드입니다. Instagram 인증을 Android 또는 iOS 앱에 추가하는 데는 몇 가지 기법이 있으며 이 게시물에서는 이에 대해 다루지 않을 것이므로 계속해서 새로운 소식이 있는지 눈여겨보시기 바랍니다!
다 되었습니다!
다른 ID 공급자에 대한 샘플을 찾고 있거나 이를 통합하는 데 문제가 있는 경우 댓글을 쓰거나
GitHub 리포지토리 문제를 사용하여 알려주시면 저희가 성심껏 도와드리겠습니다!
▶ 원문 링크