Skip to content
This repository was archived by the owner on May 19, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions kakao/KakaoLoginAndroid/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
compileSdkVersion 27
buildToolsVersion "27.0.3"

defaultConfig {
applicationId "com.google.firebase.auth.kakao"
minSdkVersion 14
targetSdkVersion 25
targetSdkVersion 27
versionCode 1
versionName "1.0"

Expand All @@ -26,15 +26,25 @@ android {
}

dependencies {
compile 'com.android.support:appcompat-v7:25.3.0'
implementation 'com.android.support:appcompat-v7:27.1.0'

compile 'com.android.volley:volley:1.0.0'
implementation 'com.android.volley:volley:1.0.0'

compile 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.github.bumptech.glide:glide:3.7.0'

compile "com.kakao.sdk:usermgmt:${project.KAKAO_SDK_VERSION}"
compile 'com.google.firebase:firebase-core:10.2.0'
compile 'com.google.firebase:firebase-auth:10.2.0'
implementation "com.kakao.sdk:usermgmt:${project.KAKAO_SDK_VERSION}"
implementation 'com.google.firebase:firebase-core:12.0.1'
implementation 'com.google.firebase:firebase-auth:12.0.1'
}

configurations.all {
resolutionStrategy {
eachDependency { details ->
// Force all of the primary support libraries to use the same version.
if (details.requested.group == 'com.android.support') {
details.useVersion '27.1.0'
}
}
}
}
apply plugin: 'com.google.gms.google-services'
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,19 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);
Toolbar toolBar = (Toolbar) findViewById(R.id.toolbar);
Toolbar toolBar = findViewById(R.id.toolbar);
setSupportActionBar(toolBar);

binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
loggedInView = (LinearLayout) findViewById(R.id.logged_in_view);
loginButton = (LoginButton) findViewById(R.id.login_button);
logoutButton = (Button) findViewById(R.id.logout_button);
imageView = (ImageView) findViewById(R.id.profile_image_view);
loggedInView = findViewById(R.id.logged_in_view);
loginButton = findViewById(R.id.login_button);
logoutButton = findViewById(R.id.logout_button);
imageView = findViewById(R.id.profile_image_view);

logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
UserManagement.requestLogout(new LogoutResponseCallback() {
UserManagement.getInstance().requestLogout(new LogoutResponseCallback() {
@Override
public void onCompleteLogout() {
FirebaseAuth.getInstance().signOut();
Expand Down Expand Up @@ -104,7 +104,9 @@ private void updateUI() {
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
if (currentUser != null) {
binding.setCurrentUser(currentUser);
if (currentUser.getPhotoUrl() != null) {
if (currentUser.getPhotoUrl() == null) {
Glide.clear(imageView);
} else {
Glide.with(this)
.load(currentUser.getPhotoUrl())
.into(imageView);
Expand Down Expand Up @@ -178,10 +180,10 @@ private class KakaoSessionCallback implements ISessionCallback {
@Override
public void onSessionOpened() {
Toast.makeText(getApplicationContext(), "Successfully logged in to Kakao. Now creating or updating a Firebase User.", Toast.LENGTH_LONG).show();
String accessToken = Session.getCurrentSession().getAccessToken();
String accessToken = Session.getCurrentSession().getTokenInfo().getAccessToken();
getFirebaseJwt(accessToken).continueWithTask(new Continuation<String, Task<AuthResult>>() {
@Override
public Task<AuthResult> then(@NonNull Task<String> task) throws Exception {
public Task<AuthResult> then(@NonNull Task<String> task) {
String firebaseToken = task.getResult();
FirebaseAuth auth = FirebaseAuth.getInstance();
return auth.signInWithCustomToken(firebaseToken);
Expand Down
12 changes: 10 additions & 2 deletions kakao/KakaoLoginAndroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
buildscript {
repositories {
jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:3.1.0'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.google.gms:google-services:3.2.1'
// classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
Expand All @@ -18,6 +22,10 @@ allprojects {
repositories {
jcenter()
maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/' }
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion kakao/KakaoLoginAndroid/gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
KAKAO_SDK_VERSION=1.1.33
KAKAO_SDK_VERSION=1.9.0
103 changes: 68 additions & 35 deletions kakao/KakaoLoginServer/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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.');
Expand All @@ -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)
Copy link
Copy Markdown
Contributor

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.

.then((userRecord) => linkUserWithKakao(userId, userRecord))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 lineUID equals to the Kakao UID.

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".

Copy link
Copy Markdown
Contributor Author

@CoderSpinoza CoderSpinoza Apr 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolasgarnier

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.

  • First check with uid kakao:${userId}

    • This should be done first. Users signed up with kakao should be able to login again whether he or she has email field or not.
  • What if email is empty?

    • Email could be empty due to lack of user consent. There is no way to map this user with any existing user if email is not given. Custom provider UIDs cannot be queried from custom user claims. There are two choices.
      • Disallow creating a user
        • Users will have to re-authenticate with providers with valid email and link again by passing firebase ID token (which will be used to retrieve firebase uid on the server-side) and Kakao access token.
      • Just create a new user and deal with mapping later on user requests
    • In this sample, just go with the second choice, cuz it's just sample. :)
  1. If email is present and there is a user with the email, give custom token to user if email is verified, make users re-authenticate with other providers if not verified.

.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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 setCustomUserClaim({lineUID: lineMid})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last line, the same is:

{kakaoUID: userId}).then(() => userRecord);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it :)

}

/**
* createFirebaseToken - returns Firebase token using Firebase Admin SDK
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I would change message to error since it's really an error more than a message :)

Important point: in the Android Client application we need to display a special message when error.code === 'auth/existing-account-need-auth'; and in that case we need to link the account if the user signs-in with the existing account. To do that we need to add a new endpoints to just "link accounts" where you would pass the kakaoAccessToken and the existing firebase user's ID Token.

});

// Start the server
Expand Down
5 changes: 5 additions & 0 deletions kakao/KakaoLoginServer/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"kakao": {
"appId": -1
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing line break at end of file :)

Loading