├── .npmignore ├── .gitignore ├── examples ├── views │ ├── login.jade │ ├── index.jade │ ├── account.jade │ └── layout.jade ├── package.json └── app.js ├── lib ├── index.js ├── profile.js ├── errors │ └── naverapierror.js └── strategy.js ├── LICENSE ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git* 3 | .project 4 | .idea -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | Thumbs.db 3 | .DS_Store 4 | .idea -------------------------------------------------------------------------------- /examples/views/login.jade: -------------------------------------------------------------------------------- 1 | extends ./layout.jade 2 | 3 | block content 4 | a(href="/auth/naver") Login with Naver 5 | -------------------------------------------------------------------------------- /examples/views/index.jade: -------------------------------------------------------------------------------- 1 | extends ./layout.jade 2 | 3 | block content 4 | unless user 5 | p Welcome. Please Log-in! 6 | else 7 | p Hello, #{user.displayName}. 8 | 9 | -------------------------------------------------------------------------------- /examples/views/account.jade: -------------------------------------------------------------------------------- 1 | extends ./layout.jade 2 | 3 | block content 4 | p ID: #{user.id} 5 | 6 | p Name: #{user.displayName} 7 | 8 | p OAuth Provider: #{user.provider} -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "^4.6.1", 4 | "express-session": "^1.6.5", 5 | "jade": "^1.4.2", 6 | "passport": "^0.2.0", 7 | "passport-oauth": "^1.0.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var Strategy = require('./strategy'); 5 | 6 | 7 | /** 8 | * Expose `Strategy` directly from package. 9 | */ 10 | exports = module.exports = Strategy; 11 | 12 | /** 13 | * Export constructors. 14 | */ 15 | exports.Strategy = Strategy; 16 | 17 | -------------------------------------------------------------------------------- /examples/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Passport Example 5 | body 6 | unless user 7 | p 8 | a(href="/") Home 9 |   10 | a(href="/login") Log In 11 |   12 | else 13 | p 14 | a(href="/") Home 15 |   16 | a(href="/account") Account 17 |   18 | a(href="/logout") Log Out 19 |   20 | block content 21 | -------------------------------------------------------------------------------- /lib/profile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse Profile of User 3 | * 4 | * @param {Object|String} json 5 | * @return {Object} 6 | * @api private 7 | */ 8 | 9 | var Profile = module.exports = exports = {}; 10 | Profile.parse = function parseProfile (obj) { 11 | if (typeof obj === 'string') { 12 | obj = JSON.parse(obj); 13 | } 14 | 15 | var profile = {}; 16 | 17 | profile.id = obj['enc_id']; 18 | // @note Caution! This is *NOT* Realname! 19 | profile.displayName = obj['nickname']; 20 | profile.emails = [{ value: obj.email }]; 21 | 22 | return profile 23 | }; -------------------------------------------------------------------------------- /lib/errors/naverapierror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `NaverAPIError` error. 3 | * 4 | * References: 5 | * - http://developer.naver.com/wiki/pages/NaverLogin_Web#section-NaverLogin_Web-4.4API_ED_98_B8_EC_B6_9C_EA_B2_B0_EA_B3_BC_EC_BD_94_EB_93_9C_EC_A0_95_EC_9D_98 6 | * 7 | * @constructor 8 | * @param {String} [message] 9 | * @param {Number} [code] 10 | * @api public 11 | */ 12 | function NaverAPIError(message, code) { 13 | Error.call(this); 14 | Error.captureStackTrace(this, arguments.callee); 15 | this.name = 'NaverAPIError'; 16 | this.message = message; 17 | this.type = 'NaverAPIError'; 18 | // @note typeof Error code (API Result Code) is `string` now. 19 | // @todo Discuss about handling Error code as `Number`, not `String`. 20 | this.code = code; 21 | this.status = 500; 22 | } 23 | 24 | /** 25 | * Inherit from `Error`. 26 | */ 27 | NaverAPIError.prototype.__proto__ = Error.prototype; 28 | 29 | 30 | /** 31 | * Expose `NaverAPIError`. 32 | */ 33 | module.exports = NaverAPIError; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2014-2015 Naver Corp. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-naver", 3 | "version": "1.0.6", 4 | "description": "Naver authentication strategy for Passport", 5 | "keywords": [ 6 | "passport", 7 | "naver", 8 | "auth", 9 | "oauth", 10 | "authentication", 11 | "oauth2" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/naver/passport-naver.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/naver/passport-naver/issues" 19 | }, 20 | "author": { 21 | "name": "Young-il Cho", 22 | "email": "youngil.cho@navercorp.com", 23 | "url": "http://github.com/terzeron" 24 | }, 25 | "contributors": [ 26 | { 27 | "name": "Chanhee Kim", 28 | "email": "chanhee.kim@navercorp.com", 29 | "url": "" 30 | }, 31 | { 32 | "name": "Seunglak Choi", 33 | "email": "seunglak@gmail.com", 34 | "url": "" 35 | }, 36 | { 37 | "name": "MooYeol Lee", 38 | "email": "mooyoul@gmail.com", 39 | "url": "https://github.com/mooyoul" 40 | } 41 | ], 42 | "licenses": [ 43 | { 44 | "type": "MIT", 45 | "url": "http://www.opensource.org/licenses/MIT" 46 | } 47 | ], 48 | "main": "./lib", 49 | "dependencies": { 50 | "passport-oauth": "^1.0.0", 51 | "underscore": "^1.8.3" 52 | }, 53 | "readmeFilename": "README.md", 54 | "homepage": "https://github.com/naver/passport-naver" 55 | } 56 | -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , passport = require('passport') 3 | , session = require('express-session') 4 | , NaverStrategy = require('../lib/index.js').Strategy; 5 | 6 | // @todo Use single `var` keyword? 7 | var client_id = '************ your app client id ************'; 8 | var client_secret = '************ your app client secret ************'; 9 | var callback_url = '************ your app callback url ************'; 10 | 11 | passport.serializeUser(function(user, done) { 12 | done(null, user); 13 | }); 14 | 15 | passport.deserializeUser(function(obj, done) { 16 | done(null, obj); 17 | }); 18 | 19 | passport.use(new NaverStrategy({ 20 | clientID: client_id, 21 | clientSecret: client_secret, 22 | callbackURL: callback_url 23 | // @todo Suggest to use `state` parameter? 24 | }, function(accessToken, refreshToken, profile, done) { 25 | process.nextTick(function () { 26 | // @todo Remove necessary comment 27 | //console.log("profile="); 28 | //console.log(profile); 29 | // data to be saved in DB 30 | user = { 31 | name: profile.displayName, 32 | email: profile.emails[0].value, 33 | username: profile.displayName, 34 | provider: 'naver', 35 | naver: profile._json 36 | }; 37 | //console.log("user="); 38 | //console.log(user); 39 | return done(null, profile); 40 | }); 41 | })); 42 | 43 | var app = express(); 44 | 45 | app.use(session({secret: 'keyboard cat'})); 46 | app.use(passport.initialize()); 47 | app.use(passport.session()); 48 | 49 | app.set('view engine', 'jade'); 50 | app.set('views', __dirname + '/views/'); 51 | 52 | app.get('/', function(req, res){ 53 | res.render('index', { user: req.user }); 54 | }); 55 | 56 | app.get('/account', ensureAuthenticated, function(req, res) { 57 | console.log(req.user); 58 | res.render('account', { user: req.user }); 59 | }); 60 | 61 | app.get('/login', function(req, res){ 62 | res.render('login', { user: req.user }); 63 | }); 64 | 65 | // Setting the naver oauth routes 66 | app.get('/auth/naver', 67 | passport.authenticate('naver', null), function(req, res) { // @todo Additional handler is necessary. Remove? 68 | console.log('/auth/naver failed, stopped'); 69 | }); 70 | 71 | // creates an account if no account of the new user 72 | app.get('/auth/naver/callback', 73 | passport.authenticate('naver', { 74 | failureRedirect: '#!/auth/login' 75 | }), function(req, res) { 76 | res.redirect('/'); 77 | }); 78 | 79 | app.get('/logout', function(req, res){ 80 | req.logout(); 81 | res.redirect('/'); 82 | }); 83 | 84 | app.listen(3000); 85 | 86 | function ensureAuthenticated(req, res, next) { 87 | if (req.isAuthenticated()) { return next(); } 88 | res.redirect('/login'); 89 | } 90 | -------------------------------------------------------------------------------- /lib/strategy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var util = require('util'), 5 | _ = require('underscore'), 6 | Profile = require('./profile'), 7 | OAuth2Strategy = require('passport-oauth').OAuth2Strategy, 8 | InternalOAuthError = require('passport-oauth').InternalOAuthError, 9 | NaverAPIError = require('./errors/naverapierror'); 10 | /** 11 | * `Strategy` constructor 12 | */ 13 | function Strategy(options, verify) { 14 | options = options || {}; 15 | 16 | options.authorizationURL = options.authorizationURL || 'https://nid.naver.com/oauth2.0/authorize'; 17 | options.tokenURL = options.tokenURL || 'https://nid.naver.com/oauth2.0/token'; 18 | 19 | // @todo Deprecate note: passing of `svcType`, `authType` param via constructor. 20 | // @see https://github.com/jaredhanson/passport-facebook#re-asking-for-declined-permissions 21 | this.__options = options; 22 | 23 | OAuth2Strategy.call(this, options, verify); 24 | this.name = 'naver'; 25 | 26 | this._profileURL = options.profileURL || 'https://openapi.naver.com/v1/nid/me'; 27 | this._oauth2.setAccessTokenName('access_token'); 28 | }; 29 |   30 | /** 31 | * Inherit from `OAuthStrategy`. 32 | */ 33 | util.inherits(Strategy, OAuth2Strategy); 34 | 35 | /** 36 | * Return extra parameters to be included in the authorization request. 37 | */ 38 | Strategy.prototype.authorizationParams = function (options) { 39 | // Do not modify `options` object. 40 | // It will hurts original options object which in `passport.authenticate(..., options)` 41 | var params = _.extend({}, options); 42 | params['response_type'] = 'code'; 43 | 44 | 45 | // @see https://github.com/naver/passport-naver/commit/2d88b7aeb14ce04db81a145b2933baabba80612b 46 | // @see http://gamedev.naver.com/index.php/%EC%98%A8%EB%9D%BC%EC%9D%B8%EA%B2%8C%EC%9E%84:OAuth_2.0_API 47 | if (this.__options.svcType !== undefined) params['svctype'] = this.__options.svcType; 48 | // @see https://github.com/naver/passport-naver#re-authentication 49 | if (this.__options.authType !== undefined) params['auth_type'] = this.__options.authType; 50 | 51 | return params; 52 | }; 53 | 54 | 55 | /** 56 | * Retrieve user profile from Naver. 57 | */ 58 | Strategy.prototype.userProfile = function(accessToken, done) { 59 | // Need to use 'Authorization' header to save the access token information 60 | // If this header is not specified, the access token is passed in GET method. 61 | this._oauth2.useAuthorizationHeaderforGET(true); 62 |   63 | // User profile API 64 | this._oauth2.get(this._profileURL, accessToken, function (err, body, res) { 65 | // @note Naver API will response with status code 200 even API request was rejected. 66 | // Thus, below line will not executed until Naver API changes. 67 | if (err) { return done(new InternalOAuthError('Failed to fetch user profile', err)); } 68 | 69 | 70 | // parse the user profile API Response to JSON object 71 | var parsed = null; 72 | try{ 73 | parsed = JSON.parse(body); 74 | //console.log(parsed); 75 | } catch (err) { 76 | return done(new InternalOAuthError('Failed to parse API response', err)); 77 | } 78 | var resultcode = parsed.resultcode; 79 | var resultmessage = parsed.message; 80 | var resultbody = parsed.response; 81 | 82 | // API Response was parsed successfully, but there are no informative data. 83 | // e.g. API Server was respond with empty response 84 | if( !(resultcode && resultmessage) ){ 85 | return done(new InternalOAuthError('Empty API Response')); 86 | } 87 | 88 | // Naver API Server was respond with unsuccessful result code. 89 | // See detail response code to https://developers.naver.com/docs/login/profile 90 | if( resultcode != "00" ){ 91 | return done(new NaverAPIError(resultmessage, resultcode)); 92 | } 93 | 94 | 95 | var profile = { provider: 'naver' }; 96 | profile.id = resultbody.id; 97 | profile.displayName = resultbody.nickname; 98 | profile.emails = [{ value: resultbody.email }]; 99 | profile._json = { 100 | email: resultbody.email, 101 | nickname: resultbody.nickname, 102 | profile_image: resultbody.profile_image, 103 | age: resultbody.age, 104 | birthday: resultbody.birthday, 105 | id: resultbody.id // User Unique ID (not naver id) 106 | }; 107 | 108 | done(null, profile); 109 | 110 | }); 111 | }; 112 | 113 | /** 114 | * Expose `Strategy`. 115 | */ 116 | module.exports = Strategy; 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passport-Naver 2 | 3 | [Passport](http://passportjs.org/) strategies for authenticating with [Naver](http://www.naver.com/) 4 | using OAuth 2.0. 5 | 6 | This module lets you authenticate using Naver in your Node.js applications. 7 | By plugging into Passport, Naver authentication can be easily and 8 | unobtrusively integrated into any application or framework that supports 9 | [Connect](http://www.senchalabs.org/connect/)-style middleware, including 10 | [Express](http://expressjs.com/). 11 | 12 | ## Install 13 | 14 | $ npm install passport-naver 15 | 16 | ## Usage of OAuth 2.0 17 | 18 | #### Configure Strategy 19 | 20 | The Naver OAuth 2.0 authentication strategy authenticates users using a Naver 21 | account and OAuth 2.0 tokens. The strategy requires a `verify` callback, which 22 | accepts these credentials and calls `done` providing a user, as well as 23 | `options` specifying a client ID, client secret, and callback URL. 24 | 25 | var NaverStrategy = require('passport-naver').Strategy; 26 | 27 | passport.use(new NaverStrategy({ 28 | clientID: config.naver.clientID, 29 | clientSecret: config.naver.clientSecret, 30 | callbackURL: config.naver.callbackURL 31 | }, 32 | function(accessToken, refreshToken, profile, done) { 33 | User.findOne({ 34 | 'naver.id': profile.id 35 | }, function(err, user) { 36 | if (!user) { 37 | user = new User({ 38 | name: profile.displayName, 39 | email: profile.emails[0].value, 40 | username: profile.displayName, 41 | provider: 'naver', 42 | naver: profile._json 43 | }); 44 | user.save(function(err) { 45 | if (err) console.log(err); 46 | return done(err, user); 47 | }); 48 | } else { 49 | return done(err, user); 50 | } 51 | }); 52 | } 53 | )); 54 | 55 | 56 | #### Authenticate Requests 57 | 58 | Use `passport.authenticate()`, specifying the `'naver'` strategy, to 59 | authenticate requests. 60 | 61 | For example, as route middleware in an [Express](http://expressjs.com/) 62 | application: 63 | 64 | // Setting the naver oauth routes 65 | app.route('/auth/naver') 66 | .get(passport.authenticate('naver', { 67 | failureRedirect: '#!/auth/login' 68 | }), users.signin); 69 | 70 | // creates an account if no account of the new user 71 | app.route('/auth/naver/callback') 72 | .get(passport.authenticate('naver', { 73 | failureRedirect: '#!/auth/login' 74 | }), users.createAccount, users.authCallback); 75 | 76 | 77 | #### Re-authentication 78 | 79 | Re-authentication is the act of asking a user to re-enter their Naver password whenever they sign in your service. This is useful to prevent man-in-the-middle hijacking while the user session of Naver is alive. 80 | 81 | Here is an example that triggers re-authentication using an authType: 82 | 83 | passport.use(new NaverStrategy({ 84 | clientID: config.naver.clientID, 85 | clientSecret: config.naver.clientSecret, 86 | callbackURL: config.naver.callbackURL, 87 | svcType: 0, 88 | authType: 'reauthenticate' // enable re-authentication 89 | }, 90 | 91 | 92 | ## App Registration for the Secret Generation 93 | 94 | You need to register your application from Naver Developer Center. 95 | <[Naver Developer Center](https://developer.naver.com/openapi/register/login.nhn)> 96 | 97 | You can get client id & secret for your application after the approval process of Naver Corp. 98 | 99 | After the client id & secret are issued, assign them to the following variables. 100 | 101 | clientID: config.naver.clientID, 102 | clientSecret: config.naver.clientSecret, 103 | callbackURL: config.naver.callbackURL, 104 | 105 | 106 | ## Examples 107 | 108 | You can execute the following application from the 'examples' directory. 109 | 110 | $ npm install 111 | $ node app.js 112 | 113 | var express = require('express') 114 | , passport = require('passport') 115 | , session = require('express-session') 116 | , NaverStrategy = require('../lib/index.js').Strategy; 117 | 118 | var client_id = '************ your app client id ************'; 119 | var client_secret = '************ your app client secret ************'; 120 | var callback_url = '************ your app callback url ************'; 121 | 122 | passport.serializeUser(function(user, done) { 123 | done(null, user); 124 | }); 125 | 126 | passport.deserializeUser(function(obj, done) { 127 | done(null, obj); 128 | }); 129 | 130 | passport.use(new NaverStrategy({ 131 | clientID: client_id, 132 | clientSecret: client_secret, 133 | callbackURL: callback_url, 134 | svcType: 0 // optional. see http://gamedev.naver.com/index.php/%EC%98%A8%EB%9D%BC%EC%9D%B8%EA%B2%8C%EC%9E%84:OAuth_2.0_API 135 | }, function(accessToken, refreshToken, profile, done) { 136 | process.nextTick(function () { 137 | //console.log("profile="); 138 | //console.log(profile); 139 | // data to be saved in DB 140 | user = { 141 | name: profile.displayName, 142 | email: profile.emails[0].value, 143 | username: profile.displayName, 144 | provider: 'naver', 145 | naver: profile._json 146 | }; 147 | //console.log("user="); 148 | //console.log(user); 149 | return done(null, profile); 150 | }); 151 | })); 152 | 153 | var app = express(); 154 | 155 | app.use(session({secret: 'keyboard cat'})); 156 | app.use(passport.initialize()); 157 | app.use(passport.session()); 158 | 159 | app.set('view engine', 'jade'); 160 | app.set('views', __dirname + '/views/'); 161 | 162 | app.get('/', function(req, res){ 163 | res.render('index', { user: req.user }); 164 | }); 165 | 166 | app.get('/account', ensureAuthenticated, function(req, res) { 167 | console.log(req.user); 168 | res.render('account', { user: req.user }); 169 | }); 170 | 171 | app.get('/login', function(req, res){ 172 | res.render('login', { user: req.user }); 173 | }); 174 | 175 | // Setting the naver oauth routes 176 | app.get('/auth/naver', 177 | passport.authenticate('naver', null), function(req, res) { 178 | console.log('/auth/naver failed, stopped'); 179 | }); 180 | 181 | // creates an account if no account of the new user 182 | app.get('/auth/naver/callback', 183 | passport.authenticate('naver', { 184 | failureRedirect: '#!/auth/login' 185 | }), function(req, res) { 186 | res.redirect('/'); 187 | }); 188 | 189 | app.get('/logout', function(req, res){ 190 | req.logout(); 191 | res.redirect('/'); 192 | }); 193 | 194 | app.listen(3000); 195 | 196 | function ensureAuthenticated(req, res, next) { 197 | if (req.isAuthenticated()) { return next(); } 198 | res.redirect('/login'); 199 | } 200 | 201 | 202 | ## Thanks to 203 | 204 | - [Jared Hanson](http://github.com/jaredhanson) 205 | - Chanhee Kim(chanhee.kim@navercorp.com) 206 | 207 | ## Author 208 | 209 | - [Young-il Cho](http://github.com/terzeron) 210 | - [Seunglak Choi](http://github.com/seunglak) 211 | - [MooYeol Lee](https://github.com/mooyoul) at [Law&Company](http://lawcompany.co.kr) 212 | 213 | ## License 214 | 215 | [The MIT License](http://opensource.org/licenses/MIT) 216 | 217 | Copyright (c) 2014 Naver Corp. 218 | --------------------------------------------------------------------------------