-
Notifications
You must be signed in to change notification settings - Fork 96
Improved ways to link accounts #17
base: master
Are you sure you want to change the base?
Changes from 3 commits
fabb737
46470c1
7987dc0
43bee04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| KAKAO_SDK_VERSION=1.1.33 | ||
| KAKAO_SDK_VERSION=1.9.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,9 @@ const serviceAccount = require('./service-account.json'); | |
|
|
||
| // Kakao API request url to retrieve user profile based on access token | ||
| const requestMeUrl = 'https://kapi.kakao.com/v1/user/me?secure_resource=true'; | ||
| const accessTokenInfoUrl = 'https://kapi.kakao.com/v1/user/access_token_info'; | ||
|
|
||
| const config = require('./config.json'); // put your kakao app id in config.json | ||
|
|
||
| // Initialize FirebaseApp with service-account.json | ||
| firebaseAdmin.initializeApp({ | ||
|
|
@@ -24,7 +27,7 @@ firebaseAdmin.initializeApp({ | |
| * requestMe - Returns user profile from Kakao API | ||
| * | ||
| * @param {String} kakaoAccessToken Access token retrieved by Kakao Login API | ||
| * @return {Promiise<Response>} User profile response in a promise | ||
| * @return {Promise<Response>} User profile response in a promise | ||
| */ | ||
| function requestMe(kakaoAccessToken) { | ||
| console.log('Requesting user profile from Kakao API server.'); | ||
|
|
@@ -33,47 +36,71 @@ function requestMe(kakaoAccessToken) { | |
| headers: {'Authorization': 'Bearer ' + kakaoAccessToken}, | ||
| url: requestMeUrl, | ||
| }); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * validateToken - Returns access token info from Kakao API, | ||
| * which checks if this token is issued by this application. | ||
| * | ||
| * @param {String} kakaoAccessToken Access token retrieved by Kakao Login API | ||
| * @return {Promise<Response>} Access token info response | ||
| */ | ||
| function validateToken(kakaoAccessToken) { | ||
| console.log('Validating access token from Kakao API server.'); | ||
| return request({ | ||
| method: 'GET', | ||
| headers: {'Authorization': 'Bearer ' + kakaoAccessToken}, | ||
| url: accessTokenInfoUrl, | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * updateOrCreateUser - Update Firebase user with the give email, create if | ||
| * updateOrCreateUser - Update Firebase user with the give email, or create if | ||
| * none exists. | ||
| * | ||
| * @param {String} userId user id per app | ||
| * @param {String} email user's email address | ||
| * @param {String} displayName user | ||
| * @param {String} photoURL profile photo url | ||
| * @return {Prommise<UserRecord>} Firebase user record in a promise | ||
| * @return {Promise<UserRecord>} Firebase user record in a promise | ||
| */ | ||
| function updateOrCreateUser(userId, email, displayName, photoURL) { | ||
| console.log('updating or creating a firebase user'); | ||
| const updateParams = { | ||
| provider: 'KAKAO', | ||
| displayName: displayName, | ||
| }; | ||
| if (displayName) { | ||
| updateParams['displayName'] = displayName; | ||
| } else { | ||
| updateParams['displayName'] = email; | ||
| } | ||
| if (photoURL) { | ||
| updateParams['photoURL'] = photoURL; | ||
| } | ||
| console.log(updateParams); | ||
| return firebaseAdmin.auth().updateUser(userId, updateParams) | ||
| .catch((error) => { | ||
| if (error.code === 'auth/user-not-found') { | ||
| updateParams['uid'] = userId; | ||
| if (email) { | ||
| updateParams['email'] = email; | ||
| console.log(`fetching a firebase user by email ${email}`); | ||
| return firebaseAdmin.auth().getUserByEmail(email) | ||
| .then((userRecord) => linkUserWithKakao(userId, userRecord)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here you should check that the user is a Kakao user by check ing that he has an attribute Then if the user is already a Kakao user, no need to link him again. Just return the user. If the user is existing but he is NOT a Kakao user you need to "link" the user. Normally you should ask the user to sign-in using the other provider first (e.g. Google or Facebook) and only once he signed-in you can mark him as "linked".
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about utilizing email_verified field that most oauth providers provide? This can simplify login process since re-authenticating with facebook or google is not necessary. Here is the modified logic.
|
||
| .catch((error) => { | ||
| if (error.code === 'auth/user-not-found') { | ||
| const params = { | ||
| uid: `kakao:${userId}`, | ||
| displayName: displayName, | ||
| }; | ||
| if (email) { | ||
| params['email'] = email; | ||
| } | ||
| if (photoURL) { | ||
| params['photoURL'] = photoURL; | ||
| } | ||
| console.log(`creating a firebase user with email ${email}`); | ||
| return firebaseAdmin.auth().createUser(params); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you are creating a user with Kakao you should mark it as being a Kakao user using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you are right. :) Added the logic. |
||
| } | ||
| return firebaseAdmin.auth().createUser(updateParams); | ||
| } | ||
| throw error; | ||
| }); | ||
| }; | ||
| throw error; | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * linkUserWithKakao - Link current user record with kakao app user id. | ||
| * | ||
| * @param {String} userId | ||
| * @param {admin.auth.UserRecord} userRecord | ||
| * @return {Promise<UserRecord>} | ||
| */ | ||
| function linkUserWithKakao(userId, userRecord) { | ||
| console.log(`linking user with kakao provider with app user id ${userId}...`); | ||
| return firebaseAdmin.auth() | ||
| .setCustomUserClaims(userRecord.uid, | ||
| {kakaoUID: userId}).then((Void) => Promise.resolve(userRecord)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The last line, the same is: {kakaoUID: userId}).then(() => userRecord);
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed it :) |
||
| } | ||
|
|
||
| /** | ||
| * createFirebaseToken - returns Firebase token using Firebase Admin SDK | ||
|
|
@@ -82,13 +109,19 @@ function updateOrCreateUser(userId, email, displayName, photoURL) { | |
| * @return {Promise<String>} Firebase token in a promise | ||
| */ | ||
| function createFirebaseToken(kakaoAccessToken) { | ||
| return requestMe(kakaoAccessToken).then((response) => { | ||
| return validateToken(kakaoAccessToken).then((response) => { | ||
| const body = JSON.parse(response); | ||
| const appId = body.appId; | ||
| if (appId !== config.kakao.appId) { | ||
| throw new Error('The given token does not belong to this application.'); | ||
| } | ||
| return requestMe(kakaoAccessToken); | ||
| }).then((response) => { | ||
| const body = JSON.parse(response); | ||
| console.log(body); | ||
| const userId = `kakao:${body.id}`; | ||
| const userId = body.id; | ||
| if (!userId) { | ||
| return res.status(404) | ||
| .send({message: 'There was no user with the given access token.'}); | ||
| throw new Error('There was no user with the given access token.'); | ||
| } | ||
| let nickname = null; | ||
| let profileImage = null; | ||
|
|
@@ -103,7 +136,7 @@ function createFirebaseToken(kakaoAccessToken) { | |
| console.log(`creating a custom firebase token based on uid ${userId}`); | ||
| return firebaseAdmin.auth().createCustomToken(userId, {provider: 'KAKAO'}); | ||
| }); | ||
| }; | ||
| } | ||
|
|
||
|
|
||
| // create an express app and use json body parser | ||
|
|
@@ -126,7 +159,7 @@ app.post('/verifyToken', (req, res) => { | |
| createFirebaseToken(token).then((firebaseToken) => { | ||
| console.log(`Returning firebase token to user: ${firebaseToken}`); | ||
| res.send({firebase_token: firebaseToken}); | ||
| }); | ||
| }).catch((error) => res.status(401).send({message: error})); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe I would change Important point: in the Android Client application we need to display a special message when |
||
| }); | ||
|
|
||
| // Start the server | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "kakao": { | ||
| "appId": -1 | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing line break at end of file :) |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there some cases where the email is empty? In this case I think we'd need to check that first. IF the email is empty you can look for the user using
getUser('kakao:${userId}')he should already be a Kakao user for sure.