10000 Merge branch 'Features/new-auth' into develop · oauth-io/sdk-node@47a9c28 · GitHub
[go: up one dir, main page]

Skip to content

Commit 47a9c28

Browse files
committed
Merge branch 'Features/new-auth' into develop
2 parents e300cdd + 016087f commit 47a9c28

File tree

8 files changed

+462
-159
lines changed

8 files changed

+462
-159
lines changed

README.md

Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,42 @@ Let's say your endpoint will be on /oauth/state_token :
107107

108108
```JavaScript
109109
app.get('/oauth/state_token', function (req, res) {
110-
var token = OAuth.generateStateToken(req);
110+
var token = OAuth.generateStateToken(req.session);
111111

112112
res.send(200, {
113113
token:token
114114
});
115115
});
116116
```
117117

118-
**Creating an authentication endpoint**
118+
**Authentication**
119+
120+
The SDK gives you an `auth` method that allows you to retrieve a `request_object`. That `request_object` allows you to make API calls, and contains the access token.
121+
122+
The `auth` method takes :
123+
124+
1. a provider name
125+
2. the session array
126+
3. (optional) an options object
127+
128+
It returns a promise to let you handle the callback and error management.
129+
130+
```
131+
OAuth.auth('provider', req.session, {
132+
option_field: option_value,
133+
//...
134+
});
135+
```
136+
137+
The options object can contain the following fields :
138+
139+
- code: an OAuth code, that will be used to get credentials from the provider and build a request_object
140+
- credentials: a credentials object, that will be used to rebuild a refreshed request_object
141+
- force_refresh: forces the credentials refresh if a refresh token is available
142+
143+
If nothing is given in the options object, the auth method tries to build a request_object from the session.
144+
145+
*Authenticating the user for the first time*
119146

120147
When you launch the authentication flow from the front-end (that is to say when you show a popup to the user so that he can allow your app to use his/her data), you'll be given a code (see next section, "Integrating the front-end SDK" to learn how to get the code).
121148

@@ -125,26 +152,29 @@ To do that, you have to create an authentication endpoint on your backend. This
125152

126153
```JavaScript
127154
app.post('/api/signin', function (req, res) {
128-
OAuth.auth(JSON.parse(req.body).code, req)
129-
.then(function (result) {
130-
//result contains the access_token if OAuth 2.0
131-
//or the couple oauth_token,oauth_token_secret if OAuth 1.0
155+
var code = JSON.parse(req.body).code;
156+
157+
// Here the auth method takes the field 'code'
158+
// in its options object. It will thus use that code
159+
// to retrieve credentials from the provider.
160+
OAuth.auth('facebook', req.session, {
161+
code: code
162+
})
163+
.then(function (request_object) {
164+
// request_object contains the access_token if OAuth 2.0
165+
// or the couple oauth_token,oauth_token_secret if OAuth 1.0
132166

133-
//result also contains methods get|post|patch|put|delete|me
134-
result.get('/me')
135-
.then(function (info) {
136-
var user = {
137-
email: info.email,
138-
firstname: info.first_name,
139-
lastname: info.last_name
140-
};
141-
//login your user here.
142-
res.send(200, 'Successfully logged in');
143-
})
144-
.fail(function (e) {
145-
//handle errors here
146-
res.send(500, 'An error occured');
147-
});
167+
// request_object also contains methods get|post|patch|put|delete|me
168+
return request_object.get('/me');
169+
})
170+
.then(function (info) {
171+
var user = {
172+
email: info.email,
173+
firstname: info.first_name,
174+
lastname: info.last_name
175+
};
176+
//login your user here.
177+
res.send(200, 'Successfully logged in');
148178
})
149179
.fail(function (e) {
150180
//handle errors here
@@ -153,19 +183,21 @@ app.post('/api/signin', function (req, res) {
153183
});
154184
```
155185

156-
**Use the authentication info in other endpoints**
186+
*Authenticating a user from the session*
157187

158-
Once a user is authenticaded on a service, the auth result object is stored
188+
Once a user is authenticaded on a service, the credentials are stored
159189
in the session. You can access it very easily from any other endpoint to use it. Let's say for example that you want to post something on your user's wall on Facebook :
160190

161191
```JavaScript
162192
app.post('/api/wall_message', function (req, res){
163193
var data = JSON.parse(req.body);
164194
//data contains field "message", containing the message to post
165195

166-
OAuth.create(req, 'facebook')
167-
.post('/me/feed', {
168-
message: data.message
196+
OAuth.auth('facebook', req.session)
197+
.then(function (request_object) {
198+
return request_object.post('/me/feed', {
199+
message: data.message
200+
});
169201
})
170202
.then(function (r) {
171203
//r contains Facebook's response, normaly an id
@@ -180,6 +212,66 @@ app.post('/api/wall_message', function (req, res){
180212
});
181213
```
182214

215+
*Authenticating a user from saved credentials*
216+
217+
* Saving credentials
218+
If you want to save the credentials to use them when the user is offline, (e.g. in a cron loading information), you can save the credentials in the data storage of your choice. All you need to do is to retrieve the credentials object from the request_object :
219+
220+
```javascript
221+
OAuth.auth('provider', req.session, {
222+
code: code
223+
})
224+
.then(function (request_object) {
225+
var credentials = request_object->getCredentials();
226+
227+
// Here you can save the credentials object wherever you want
228+
229+
});
230+
```
231+
232+
* Using saved credentials
233+
234+
You can then rebuild a request_object from the credentials you saved earlier :
235+
```javascript
236+
// Here you retrieved the credentials object from your data storage
237+
238+
OAuth.auth('provider', req.session, {
239+
credentials: credentials
240+
})
241+
.then(function (request_object) {
242+
// Here request_object has been rebuilt from the credentials object
243+
// If the credentials are expired and contain a refresh token,
244+
// the auth method automatically refresh them.
245+
});
246+
247+
```
248+
249+
*Refreshing saved credentials*
250+
251+
Tokens are automatically refreshed when you use the `auth` method with the session or with saved credentials. The SDK checks that the access token is expired whenever it's called.
252+
253+
If it is, and if a refresh token is available in the credentials (you may have to configure the OAuth.io app in a specific way for some providers), it automatically calls the OAuth.io refresh token endpoint.
254+
255+
If you want to force a refresh from the `auth` method, you can pass the `force_refresh` field in the option object, like this :
256+
257+
```javascript
258+
OAuth.auth('provider', req.session, {
259+
force_refresh: true
260+
});
261+
```
262+
263+
You can also refresh a credentials object manually. To do that, call the OAuth.refreshCredentials on the request_object or on a credentials object :
264+
265+
```javascript
266+
OAuth.refreshCredentials(request_object, req.session)
267+
.then(function (request_object) {
268+
// Here request_object has been refreshed
269+
})
270+
.fail(function (e) {
271+
// Handle an error
272+
});
273+
```
274+
183275
**3. Integrating Front-end SDK**
184276

185277
This SDK is available on our website : [Get it on oauth.io][1].

coffee/lib/authentication.coffee

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,88 @@ request = require 'request'
22
Q = require 'q'
33

44
module.exports = (cache, requestio) ->
5-
return {
6-
authenticate: (code, req) ->
5+
a = {
6+
refresh_tokens: (credentials, session, force) ->
7+
defer = Q.defer()
8+
credentials.refreshed = false
9+
now = new Date()
10+
if credentials.refresh_token and ((credentials.expires and now.getTime() > credentials.expires) or force)
11+
request.post {
12+
url: cache.oauthd_url + '/auth/refresh_token/' + credentials.provider,
13+
form: {
14+
token: credentials.refresh_token,
15+
key: cache.public_key,
16+
secret: cache.secret_key
17+
}
18+
}, (e, r, body) ->
19+
if (e)
20+
defer.reject e
21+
return defer.promise
22+
else
23+
if typeof body is "string"
24+
try
25+
body = JSON.parse body
26+
catch e
27+
defer.reject e
28+
if typeof body == "object" and body.access_token and body.expires_in
29+
credentials.expires = new Date().getTime() + body.expires_in * 1000
30+
for k of body
31+
credentials[k] = body[k]
32+
if (session?)
33+
session.oauth = session.oauth || {}
34+
session.oauth[credentials.provider] = credentials
35+
credentials.refreshed = true
36+
credentials.last_refresh = new Date().getTime()
37+
defer.resolve credentials
38+
else
39+
defer.resolve credentials
40+
else
41+
defer.resolve credentials
42+
return defer.promise
43+
auth: (provider, session, opts) ->
44+
defer = Q.defer()
45+
46+
if opts?.code
47+
return a.authenticate(opts.code, session)
48+
49+
if opts?.credentials
50+
a.refresh_tokens(opts.credentials, session, opts?.force_refresh)
51+
.then (credentials) ->
52+
defer.resolve(a.construct_request_object(credentials))
53+
return defer.promise
54+
if (not opts?.credentials) and (not opts?.code)
55+
if session.oauth[provider]
56+
a.refresh_tokens(session.oauth[provider], session, opts?.force_refresh)
57+
.then (credentials) ->
58+
defer.resolve(a.construct_request_object(credentials))
59+
else
60+
defer.reject new Error('Cannot authenticate from session for provider \'' + provider + '\'')
61+
return defer.promise
62+
63+
defer.reject new Error('Could not authenticate, parameters are missing or wrong')
64+
return defer.promise
65+
construct_request_object: (credentials) ->
66+
request_object = {}
67+
for k of credentials
68+
request_object[k] = credentials[k]
69+
request_object.get = (url, options) ->
70+
return requestio.make_request(request_object, 'GET', url, options)
71+
request_object.post = (url, options) ->
72+
return requestio.make_request(request_object, 'POST',url, options)
73+
request_object.patch = (url, options) ->
74+
return requestio.make_request(request_object, 'PATCH', url, options)
75+
request_object.put = (url, options) ->
76+
return requestio.make_request(request_object, 'PUT', url, options)
77+
request_object.del = (url, options) ->
78+
return requestio.make_request(request_object, 'DELETE', url, options)
79+
request_object.me = (options) ->
80+
return requestio.make_me_request(request_object, options)
81+
request_object.getCredentials = () ->
82+
return credentials
83+
request_object.wasRefreshed = () ->
84+
return credentials.refreshed
85+
return request_object
86+
authenticate: (code, session) ->
787
defer = Q.defer()
888
request.post {
989
url: cache.oauthd_url + '/auth/access_token',
@@ -26,25 +106,16 @@ module.exports = (cache, requestio) ->
26106
if (not response.state?)
27107
defer.reject new Error 'State is missing from response'
28108
return
29-
30-
if (not req?.session?.csrf_tokens? or response.state not in req.session.csrf_tokens)
109+
if (not session?.csrf_tokens? or response.state not in session.csrf_tokens)
31110
defer.reject new Error 'State is not matching'
32-
33-
response.get = (url, options) ->
34-
return requestio.make_request(response, 'GET', url, options)
35-
response.post = (url, options) ->
36-
return requestio.make_request(response, 'POST',url, options)
37-
response.patch = (url, options) ->
38-
return requestio.make_request(response, 'PATCH', url, options)
39-
response.put = (url, options) ->
40-
return requestio.make_request(response, 'PUT', url, options)
41-
response.del = (url, options) ->
42-
return requestio.make_request(response, 'DELETE', url, options)
43-
response.me = (options) ->
44-
return requestio.make_me_request(response, options)
45-
if (req?.session?)
46-
req.session.oauth = req.session.oauth || {}
47-
req.session.oauth[response.provider] = response
111+
if response.expires_in
112+
response.expires = new Date().getTime() + response.expires_in * 1000
113+
response = a.construct_request_object response
114+
if (session?)
115+
session.oauth = session.oauth || {}
116+
session.oauth[response.provider] = response
48117
defer.resolve response
49118
return defer.promise
119+
50120
}
121+
return a

coffee/lib/csrf_generator.coffee

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = (guid) ->
2-
return (req) ->
2+
return (session) ->
33
csrf_token = guid()
4-
req.session.csrf_tokens = req.session.csrf_tokens or []
5-
req.session.csrf_tokens.push csrf_token
6-
req.session.csrf_tokens.shift() if req.session.csrf_tokens.length > 4
4+
session.csrf_tokens = session.csrf_tokens or []
5+
session.csrf_tokens.push csrf_token
6+
session.csrf_tokens.shift() if session.csrf_tokens.length > 4
77
return csrf_token

coffee/main.coffee

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@ cache = {
1313
}
1414

1515
module.exports = ->
16-
17-
1816
guid = _guid()
1917
csrf_generator = _csrf_generator(guid)
2018
requestio = _requestio(cache)
2119
authentication = _authentication(cache, requestio)
2220
endpoints_initializer = _endpoints_initializer(csrf_generator, cache, authentication)
23-
24-
2521

2622
return {
2723
initialize: (app_public_key, app_secret_key) ->
@@ -40,40 +36,21 @@ module.exports = ->
4036
return cache.public_key
4137
getAppSecret: ->
4238
return cache.secret_key
43-
getCsrfTokens: (req) ->
44-
return req.session.csrf_tokens
39+
getCsrfTokens: (session) ->
40+
return session.csrf_tokens
4541
setOAuthdUrl: (url) ->
4642
cache.oauthd_url = url
4743
getOAuthdUrl: ->
4844
return cache.oauthd_url
4945
getVersion: ->
5046
package_info.version
51-
generateStateToken: (req) ->
52-
csrf_generator(req)
47+
generateStateToken: (session) ->
48+
csrf_generator(session)
5349
initEndpoints: (app) ->
5450
endpoints_initializer app
55-
auth: (code, req) ->
56-
authentication.authenticate code, req
57-
create: (req, provider_name) ->
58-
response = req?.session?.oauth?[provider_name]
59-
if not response?
60-
response = {
61-
error: true
62-
provider: provider_name
63-
}
64-
65-
response.get = (url) ->
66-
return requestio.make_request(response, 'GET', url)
67-
response.post = (url, options) ->
68-
return requestio.make_request(response, 'POST',url, options)
69-
response.patch = (url, options) ->
70-
return requestio.make_request(response, 'PATCH', url, options)
71-
response.put = (url, options) ->
72-
return requestio.make_request(response, 'PUT', url, options)
73-
response.del = (url, options) ->
74-
return requestio.make_request(response, 'DELETE', url, options)
75-
response.me = (options) ->
76-
return requestio.make_me_request(response, options)
77-
return response
51+
auth: (provider, session, opts) ->
52+
authentication.auth provider, session, opts
53+
refreshCredentials: (credentials, session) ->
54+
return authentication.refresh_tokens credentials, session, true
7855
}
7956

0 commit comments

Comments
 (0)
0