@activelylearn/passport-clever-oauth20 中文文档教程
Supporting Clever Instant Login
Clever 通过即时登录 与许多应用程序类型集成。 Instant Login 由 OAuth2 标准提供支持,因此可以轻松快速地使用我们选择的技术。 让我们看一下使用 PassportJS 框架的示例 NodeJS Web 应用程序。
Dependencies
App Creation
应用程序必须在 Clever 的开发人员门户中创建。 注册一个开发者帐户创建这个应用程序。 稍后可以通过浏览到开发人员门户左侧菜单的“设置”部分中的“应用程序”来找到它。 还生成了一个演示区用于测试目的。
以下是一些不容错过的资源:
App Configuration
我们已将应用程序的配置存储在一个单独的文件中。 完成此配置的值可在开发人员门户中的应用程序和区域中找到。 我们已将我们的客户端 ID 与下面的即时登录 URL 分开。 PassportJS 框架将在运行时处理此添加,这使我们无需在配置中重复客户端 ID。 我们使用 /instant-login 端点作为登录 url,但如果 /authorize 端点被替换,此应用程序的功能将相同。
var appConfig = {
'port' : 3000,
'sessionSecret' : '{SessionSecret}',
'requestUrl' : 'https://api.clever.com',
'oauth' : {
// credentials
'clientId' : '{ClientId}',
'clientSecret' : '{ClientSecret}',
// instant clever login urls
'instantLoginUrl' : 'https://clever.com/oauth/instant-login?district_id={DistrictId}',
'instantCallbackUrl' : 'https://{DomainName}/auth/clever-instant/callback',
// token url
'tokenUrl' : 'https://clever.com/oauth/tokens?grant_type=authorization_code'
}
};
module.exports = appConfig;
注意:占位符值用大括号括起来。
Node.js Modules
我们将使用一些 Node.js 依赖项和上面的配置。 我们确实遇到了一个小问题,即 OAuth2Strategy 不符合 Clever 将客户端凭据传递到令牌端点的要求。 内置策略在查询字符串中传递这些值,但 Clever 需要在基本身份验证标头中使用它们。 确实只是几行需要更改,但它们在框架的深处。 为了简单起见,我们将在下面忽略这个细节,但为了清楚起见,您可以比较 clever-passport-oauth 文件夹中的 bak 文件。
var express = require('express');
var passport = require('passport');
//var OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
var OAuth2Strategy = require('./clever-passport-oauth/index').OAuth2Strategy;
var request = require('request');
var appConfig = require('./appConfig');
注意:Passport 的 OAuth2 策略经过定制以支持 Clever 的令牌 URL。
App Setup
有一些必要的设置来配置 NodeJS 框架。 它与我们的应用程序的目的无关,但我们还是会在这里展示它。
var app = express();
app.set('port', (process.env.PORT || appConfig.port));
app.use(express.static(__dirname + '/public'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: appConfig.sessionSecret, resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
Passport for OAuth
Passport Configuration
这个 PassportJS 框架将为我们处理 OAuth 细节。 我们必须给它一些配置和指导来解析和持久化返回的数据。
// setup instant login through passport
passport.use('clever-instant', new OAuth2Strategy({
authorizationURL: appConfig.oauth.instantLoginUrl,
tokenURL: appConfig.oauth.tokenUrl,
clientID: appConfig.oauth.clientId,
clientSecret: appConfig.oauth.clientSecret,
callbackURL: appConfig.oauth.instantCallbackUrl
},
function(accessToken, refreshToken, profile, done) {
var fakeUser = { 'accessToken' : accessToken };
var options = { 'method' : 'GET',
'uri' : '/me'
};
var callback = function(user, data) {
var fullUser = null;
if(data && data.data) {
fullUser = data.data;
fullUser.accessToken = user.accessToken;
}
done(null, fullUser);
};
handleRequest(fakeUser,options,callback);
}
));
Passport Persistence
这个演示使用了一个朴素的持久层。 你会想要更耐用的东西。 PassportJS 框架只需要在请求之间存储用户的令牌。
var users = [];
// retrieve user for request
function findUser(request) {
return users[request.session.passport.user];
};
// store user in persistent storage for passport
passport.serializeUser(function(user, done) {
users[user.id] = user;
done(null, user.id);
});
// retrieve user from persistent store for passport
passport.deserializeUser(function(id, done) {
var user = users[id];
done(null, user);
});
// require token for session enabled request
function requireToken() {
return function(req, res, next) {
if (req.session.passport &&
req.session.passport.user &&
findUser(req)) {
next();
}
else {
res.redirect('/login');
}
}
}
Application Logic
Common Utilities
我们需要进行 API 调用,而此函数将使调用变得更容易一些。
// simplified request handling
function handleRequest(user, options, callback) {
var headers = { 'Authorization': 'Bearer ' + user.accessToken,
'Content-Type': 'application/json',
'User-Agent': 'My App'
};
request({
'uri' : appConfig.requestUrl + options.uri,
'method' : 'GET',
'headers' : headers
},
function(error, response, body) {
if(error) {
callback(user,null);
return;
}
if (response.statusCode === 200) {
callback(user,JSON.parse(body));
} else {
callback(user,response.statusCode);
}
});
};
App URLs
我们需要公开将我们的应用程序流量路由到 Passport 功能的 URL。
// entry point for clever instant auth
app.get('/auth/clever-instant', passport.authenticate('clever-instant'));
// callback for clever auth
app.get('/auth/clever-instant/callback',
passport.authenticate('clever-instant', { successRedirect: '/',
failureRedirect: '/login'
}));
// login setup
app.get('/login', function(req, res){
res.send('<a href="/auth/clever/instant"><img src="images/sign-in-with-clever-full.png" /></a>');
});
// list the user's profile if authenticated
app.get('/',requireToken(), function(request, response) {
var options = { 'method' : 'GET',
'uri' : '/me'
};
var callback = function(user, data) {
response.send('<pre>' + JSON.stringify(data,null,' ') + '</pre>');
};
handleRequest(findUser(request),options,callback);
});
App Launch
最后,我们只启动服务器。
app.listen(app.get('port'), function() {
console.log('Node app is running on port', app.get('port'));
});
Application Experience
测试应用程序时,请期待以下内容:
- The "Login with Clever" button will greet you.
- This button will launch Clever's demo login screen.
- Successful login will diplay the user's profile in raw JSON.
享受!
Supporting Clever Instant Login
Clever integrates with many application types through Instant Login. Instant Login is powered by the OAuth2 standard, so it's easy to get started quickly in our technology of choice. Let's take a look at an example NodeJS web application using the PassportJS framework.
Dependencies
App Creation
An application must be created in Clever's developer portal. Signing up for a developer account creates this application. It can be found later by browsing to Applications in the Settings section of left-hand menu of the developer portal. A demo district is also generated for testing purposes.
Here are a few resources that should not be missed:
App Configuration
We've stored configuration for our app in a separate file. The values to complete this configuration are found in your application and district within the developer portal. We've separated our client id from the instant login url below. The PassportJS framework will handle this addition at runtime, and this relieves us from repeating the client id in our configuration. We have used the /instant-login endpoint for the login url, but this application would function the same if the /authorize endpoint were substituted.
var appConfig = {
'port' : 3000,
'sessionSecret' : '{SessionSecret}',
'requestUrl' : 'https://api.clever.com',
'oauth' : {
// credentials
'clientId' : '{ClientId}',
'clientSecret' : '{ClientSecret}',
// instant clever login urls
'instantLoginUrl' : 'https://clever.com/oauth/instant-login?district_id={DistrictId}',
'instantCallbackUrl' : 'https://{DomainName}/auth/clever-instant/callback',
// token url
'tokenUrl' : 'https://clever.com/oauth/tokens?grant_type=authorization_code'
}
};
module.exports = appConfig;
Note: Placeholder values are surrounded in braces.
Node.js Modules
We'll use a few Node.js dependencies and our config from above. We did run into one small issue where OAuth2Strategy did not match Clever's requirement of passing the client credentials to the token endpoint. The built-in strategy passed these values in the query string, but Clever requires them in a basic authentication header. It was really just a couple of lines that needed to change, but they were deep in the framework. We will gloss over this detail below for simplicity, but you can compare bak files in the clever-passport-oauth folder for clarity.
var express = require('express');
var passport = require('passport');
//var OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
var OAuth2Strategy = require('./clever-passport-oauth/index').OAuth2Strategy;
var request = require('request');
var appConfig = require('./appConfig');
Note: Passport's OAuth2 strategy was customized to support Clever's token URL.
App Setup
There is some necessary setup to get the NodeJS frameworks configured. It has little to do with our application's purpose, but we'll show it here anyway.
var app = express();
app.set('port', (process.env.PORT || appConfig.port));
app.use(express.static(__dirname + '/public'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: appConfig.sessionSecret, resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
Passport for OAuth
Passport Configuration
This PassportJS framework will handle the OAuth details for us. We must give it some configuration and direction on parsing and persisting the returned data.
// setup instant login through passport
passport.use('clever-instant', new OAuth2Strategy({
authorizationURL: appConfig.oauth.instantLoginUrl,
tokenURL: appConfig.oauth.tokenUrl,
clientID: appConfig.oauth.clientId,
clientSecret: appConfig.oauth.clientSecret,
callbackURL: appConfig.oauth.instantCallbackUrl
},
function(accessToken, refreshToken, profile, done) {
var fakeUser = { 'accessToken' : accessToken };
var options = { 'method' : 'GET',
'uri' : '/me'
};
var callback = function(user, data) {
var fullUser = null;
if(data && data.data) {
fullUser = data.data;
fullUser.accessToken = user.accessToken;
}
done(null, fullUser);
};
handleRequest(fakeUser,options,callback);
}
));
Passport Persistence
A naive persistence layer is used for this demo. You'll want something more durable. The PassportJS framework just needs to store a user's tokens between request.
var users = [];
// retrieve user for request
function findUser(request) {
return users[request.session.passport.user];
};
// store user in persistent storage for passport
passport.serializeUser(function(user, done) {
users[user.id] = user;
done(null, user.id);
});
// retrieve user from persistent store for passport
passport.deserializeUser(function(id, done) {
var user = users[id];
done(null, user);
});
// require token for session enabled request
function requireToken() {
return function(req, res, next) {
if (req.session.passport &&
req.session.passport.user &&
findUser(req)) {
next();
}
else {
res.redirect('/login');
}
}
}
Application Logic
Common Utilities
We'll need to make API calls, and this function will make that a little easier.
// simplified request handling
function handleRequest(user, options, callback) {
var headers = { 'Authorization': 'Bearer ' + user.accessToken,
'Content-Type': 'application/json',
'User-Agent': 'My App'
};
request({
'uri' : appConfig.requestUrl + options.uri,
'method' : 'GET',
'headers' : headers
},
function(error, response, body) {
if(error) {
callback(user,null);
return;
}
if (response.statusCode === 200) {
callback(user,JSON.parse(body));
} else {
callback(user,response.statusCode);
}
});
};
App URLs
We need to expose URLs that route our applications' traffic to Passport's functionality.
// entry point for clever instant auth
app.get('/auth/clever-instant', passport.authenticate('clever-instant'));
// callback for clever auth
app.get('/auth/clever-instant/callback',
passport.authenticate('clever-instant', { successRedirect: '/',
failureRedirect: '/login'
}));
// login setup
app.get('/login', function(req, res){
res.send('<a href="/auth/clever/instant"><img src="images/sign-in-with-clever-full.png" /></a>');
});
// list the user's profile if authenticated
app.get('/',requireToken(), function(request, response) {
var options = { 'method' : 'GET',
'uri' : '/me'
};
var callback = function(user, data) {
response.send('<pre>' + JSON.stringify(data,null,' ') + '</pre>');
};
handleRequest(findUser(request),options,callback);
});
App Launch
Finally, we just start the server.
app.listen(app.get('port'), function() {
console.log('Node app is running on port', app.get('port'));
});
Application Experience
Expect the following when testing the application:
- The "Login with Clever" button will greet you.
- This button will launch Clever's demo login screen.
- Successful login will diplay the user's profile in raw JSON.
Enjoy!