Instagram은 앱을
승인하고 Instagram 사용자의 ID를 포함하는 사용자 데이터에 액세스하기 위한 주요 경로로써 OAuth 2.0을 지원합니다. 이 경우, 사용자가 OAuth 2.0 인증 코드 흐름을 거치고 앱에 액세스 권한을 부여하도록 해야 합니다. 다음은 OAuth 2.0 흐름의 진행 방식입니다.
우선, Instagram의 승인 엔드포인트로 사용자를 리디렉션해야 합니다. 그러면 Instagram에서는 사용자에게 개발자의 앱에 대한 액세스 권한을 부여하도록 최초 요청 시 동의 작업을 팝업 창을 통해 수행합니다.
사용자가 앱을 승인한 후에는 인증 코드와 함께 도메인으로 다시 리디렉션됩니다. 서버 쪽에서는 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 리포지토리 문제를 사용하여 알려주시면 저희가 성심껏 도와드리겠습니다!
▶ 원문 링크