feat(refresh_tokens): add refresh_tokens to /token endpoint #260
Conversation
| @@ -148,13 +151,20 @@ const QUERY_CODE_INSERT = | |||
| 'INSERT INTO codes (clientId, userId, email, scope, authAt, code) ' + | |||
| 'VALUES (?, ?, ?, ?, ?, ?)'; | |||
| const QUERY_TOKEN_INSERT = | |||
| 'INSERT INTO tokens (clientId, userId, email, scope, type, token) VALUES ' + | |||
| '(?, ?, ?, ?, ?, ?)'; | |||
| 'INSERT INTO tokens (clientId, userId, email, scope, type, expiresAt,token)' + | |||
pdehaan
May 26, 2015
Contributor
Add space before token?
Add space before token?
| 'INSERT INTO tokens (clientId, userId, email, scope, type, token) VALUES ' + | ||
| '(?, ?, ?, ?, ?, ?)'; | ||
| 'INSERT INTO tokens (clientId, userId, email, scope, type, expiresAt,token)' + | ||
| ' VALUES (?, ?, ?, ?, ?, ?)'; |
pdehaan
May 26, 2015
Contributor
Is this missing a "?"? It seems like we added token above and have 7 fields, but only 6 ?s.
Is this missing a "?"? It seems like we added token above and have 7 fields, but only 6 ?s.
| @@ -362,7 +362,26 @@ particular user. | |||
|
|
|||
| - `client_id`: The id returned from client registration. | |||
| - `client_secret`: The secret returned from client registration. | |||
| - `code`: A string that was received from the [authorization][] endpoint. | |||
| - `ttl`: (optional) Seconds that this access_token should be valid. | |||
vladikoff
May 27, 2015
Contributor
nit: Seconds that this access_token should be valid. ->
The amount of time the access_token is valid for in seconds or
The auth_token expiration time in seconds
nit: Seconds that this access_token should be valid. ->
The amount of time the access_token is valid for in seconds or
The auth_token expiration time in seconds
rfk
Jun 2, 2015
Member
Suggest bolding Seconds here like you've done in the lists below
Suggest bolding Seconds here like you've done in the lists below
| @@ -80,10 +80,15 @@ const conf = convict({ | |||
| } | |||
| }, | |||
| expiration: { | |||
| code: { | |||
| accessTokenS: { | |||
vladikoff
May 27, 2015
Contributor
nit: expiration.accessTokenInSeconds? is this format used anywhere else? (adding S and Ms)?
nit: expiration.accessTokenInSeconds? is this format used anywhere else? (adding S and Ms)?
seanmonstar
May 27, 2015
Author
Member
I don't know if we use this convention anywhere else. I think we try to use milliseconds whenever we can, but the spec claims that the expires_at value should be in seconds...
I don't know if we use this convention anywhere else. I think we try to use milliseconds whenever we can, but the spec claims that the expires_at value should be in seconds...
rfk
May 28, 2015
Member
I like millisecond timestamps too, but I think it will be against the grain when interfacing with existing OAuth, JWT and other related specs, which all seem to use timestamps in seconds.
For example, this was one of the trickest parts of trying to bring the legacy BrowserID assertion format into line with the final JWT spec. It caused a pretty funny (and thankfully low-impact) security bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1096106
In the interests of consistency, I suggest we grit our teeth and use integer second timestamps throughout the public API.
I like millisecond timestamps too, but I think it will be against the grain when interfacing with existing OAuth, JWT and other related specs, which all seem to use timestamps in seconds.
For example, this was one of the trickest parts of trying to bring the legacy BrowserID assertion format into line with the final JWT spec. It caused a pretty funny (and thankfully low-impact) security bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1096106
In the interests of consistency, I suggest we grit our teeth and use integer second timestamps throughout the public API.
| token.scope = vals.scope; | ||
| token.createdAt = new Date(); | ||
| var _token = unique.token(); | ||
| var ret = clone(token); |
vladikoff
May 27, 2015
Contributor
nit: group vars with the top var token = {}. rename var token = {}; to something like tokenData = {} to avoid _token vs token
nit: group vars with the top var token = {}. rename var token = {}; to something like tokenData = {} to avoid _token vs token
| @@ -217,7 +217,7 @@ AppError.invalidScopes = function invalidScopes(scopes) { | |||
| code: 400, | |||
| error: 'Invalid scopes', | |||
| errno: 114, | |||
| message: 'Invalid scopes' | |||
| message: 'Scopes requested not allowed' | |||
vladikoff
May 27, 2015
Contributor
Nit: Scopes requested not allowed -> i.e Requested scopes are not allowed
Nit: Scopes requested not allowed -> i.e Requested scopes are not allowed
| @@ -407,6 +457,7 @@ MysqlStore.prototype = { | |||
| // TODO this should be a transaction or stored procedure | |||
vladikoff
May 27, 2015
Contributor
Should we file a "good first bug" for this?
Should we file a "good first bug" for this?
seanmonstar
May 27, 2015
Author
Member
I'm not sure if it'd be an "easy" bug...
I'm not sure if it'd be an "easy" bug...
| } | ||
| return json; | ||
| }); | ||
| //return [code.authAt, db.removeCode(code.code), db.generateToken(code)]; |
vladikoff
May 27, 2015
Contributor
old code from generateToken left here.
old code from generateToken left here.
| endpoint specifically as a refresh token. | ||
| - `scope`: (optional) A subset of scopes provided to this | ||
| refresh_token originally, to receive an access_token with less | ||
| permissions. |
rfk
May 27, 2015
Member
refresh tokens will be mandatory
IIUC there are cases in the OAuth spec where we're forbidden from delivering refresh tokens, e.g. when using implicit grant clients. Perhaps we can weaken this to something like "refresh tokens will be teh default behaviour".
refresh tokens will be mandatory
IIUC there are cases in the OAuth spec where we're forbidden from delivering refresh tokens, e.g. when using implicit grant clients. Perhaps we can weaken this to something like "refresh tokens will be teh default behaviour".
seanmonstar
May 27, 2015
Author
Member
Woops, this was the first thing I wrote in this PR. It didn't reflect that changes that we'd always expire the token now. Updated the text.
Woops, this was the first thing I wrote in this PR. It didn't reflect that changes that we'd always expire the token now. Updated the text.
1fc027f
to
e3ab7bd
|
woo, build finally passes |
| - `refresh_token`: A string that received from the [token][] | ||
| endpoint specifically as a refresh token. | ||
| - `scope`: (optional) A subset of scopes provided to this | ||
| refresh_token originally, to receive an access_token with less |
rfk
May 28, 2015
Member
I was going to go all grammar pedant on this and suggest s/less/fewer/, but Wikipedia tells me [1] that it's actually fine to use "less" with count nouns and I should learn to relax and enjoy life without worrying about made-up grammatical rules dating from 1770...
I was going to go all grammar pedant on this and suggest s/less/fewer/, but Wikipedia tells me [1] that it's actually fine to use "less" with count nouns and I should learn to relax and enjoy life without worrying about made-up grammatical rules dating from 1770...
seanmonstar
Jun 1, 2015
Author
Member
wuts grammer
wuts grammer
| - `refresh_token`: (Optional) A refresh token to fetch a new access | ||
| token when this one expires. Only will be present if | ||
| `grant_type=authorization_code`. | ||
| - `expires_at`: **Millisecond** timestamp when this access token will no longer be valid. |
rfk
May 28, 2015
Member
Mixing seconds and milliseconds in this response seems like a recipe for trouble.
More broadly, I think it makes sense to copy the behaviour of existing oauth and/or openid-connect servers where there's precedent. In this case both specs seem to suggest using expires_in with a relative timestamp in seconds, e.g: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse
Mixing seconds and milliseconds in this response seems like a recipe for trouble.
More broadly, I think it makes sense to copy the behaviour of existing oauth and/or openid-connect servers where there's precedent. In this case both specs seem to suggest using expires_in with a relative timestamp in seconds, e.g: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse
| * token: <string>, | ||
| * clientId: <client_id>, | ||
| * userId: <user_id>, | ||
| * scope: <string>, | ||
| * createdAt: <timestamp> |
rfk
May 28, 2015
Member
Do you think it would be useful to track a lastUsedAt for refresh tokens? We might want to e.g. expire tokens that have not been used in months. Doesn't have to be part of the initial landing, but something to think about.
Do you think it would be useful to track a lastUsedAt for refresh tokens? We might want to e.g. expire tokens that have not been used in months. Doesn't have to be part of the initial landing, but something to think about.
seanmonstar
Jun 1, 2015
Author
Member
It probably would be useful, yes. Those that ignore the refresh token could be inadvertently generating new ones every time, and that would load up the table with a bunch of refresh tokens that sit there forever, ready to be guessed?
It probably would be useful, yes. Those that ignore the refresh token could be inadvertently generating new ones every time, and that would load up the table with a bunch of refresh tokens that sit there forever, ready to be guessed?
| email: vals.email, | ||
| scope: vals.scope, | ||
| createdAt: new Date(), | ||
| token: encrypt.hash(token) |
rfk
May 28, 2015
Member
nit: it may be clearer to name this e.g. tokenId or tokenHash rather than just token
nit: it may be clearer to name this e.g. tokenId or tokenHash rather than just token
rfk
May 28, 2015
Member
Hmm...I see that would be inconsistent with the naming in the mysql backend, where we use token for the column name. Probably not worth it then.
Hmm...I see that would be inconsistent with the naming in the mysql backend, where we use token for the column name. Probably not worth it then.
|
Whoops, I think I accidentally commented on the commit rather than the diff; hopefully those two comments make sense. This looks great @seanmonstar, thanks for separating the commits for review. Please rebase when ready. |
|
nits addressed, squashed into 1. |
|
I filed #275 as a follow-on for tracking last-used-at timestamps on refresh tokens. |
|
The access_type parameter will require support from the content-server; I filed mozilla/fxa-content-server#2589 @seanmonstar I'm sure you'd be most welcome to dive in and implement that part of it as well, if you were feeling adventurous :-) |
I've dug into these, and the only one that I think we need to be careful with is pocket. I'll send them an email and make sure we've got a clear path forward. |
|
(Migrating from IRC discussion) Talking to Pocket, they'd like for all of their existing access_tokens to keep working after the switch, and I think this is a fair enough request. We can achieve this by migating existing access tokens to become refresh tokens, staging the rollout like this:
To give us enough time between (2) and (3) we could increase the default access_token lifetime to something longer, so we don't break things in the meantime. Once reliers are migrated over we can drop it back down to a shorter lifetime. @seanmonstar thoughts? |
|
To upgrade Pocket's tokens: INSERT INTO refreshTokens (token, clientId, userId, email, scope, createdAt)
(SELECT token, clientId, userId, email, scope, createdAt FROM tokens WHERE clientId = '$POCKET_CLIENT_ID', createdAt < MIN(SELECT createdAt from refresh_tokens WHERE clientId = '$POCKET_CLIENT_ID'));
DELETE FROM tokens WHERE clientId = '$POCKET_CLIENT_ID', MIN(SELECT createdAt from refresh_tokens WHERE clientId = '$POCKET_CLIENT_ID');
|
@seanmonstar points out that this won't work, since expiry is based on token creation time. But thinking about it some more, this won't matter anyway. As long as pocket doesn't try to use the tokens between when they're created and when we switch them to refresh magic, nothing will break. |
|
Alternatively, default expiration could be set to something ridiculous at first, and then re-adjusted afterwards. |
|
rebased to master |
|
I think, based on IRC conversation, that we can do the above deployment plan on top of the code as it currently sits. @seanmonstar can I get an official r? from you on that plan, and if so, I think this is ready to merge. |
|
Yea that plan seems fine to me. |
| } | ||
| }); | ||
| }).then(function(res) { | ||
| assertRequestParam(res.result, 'ttl'); |
rfk
Jun 29, 2015
Member
Should this assert a particular response code?
Should this assert a particular response code?
rfk
Jun 29, 2015
Member
n/m, I see the helper function does this internally. That's not clear from the name, but I'll file a follow-up bug about it.
n/m, I see the helper function does this internally. That's not clear from the name, but I'll file a follow-up bug about it.
| payload: Joi.object().keys({ | ||
| access_token: validators.token, | ||
| refresh_token: validators.token | ||
| }).rename('token', 'access_token').xor('access_token', 'refresh_token') |
rfk
Jun 29, 2015
Member
I wonder if we should add a comment here about why it's being renamed, since we probably won't remember in a few month's time.
I wonder if we should add a comment here about why it's being renamed, since we probably won't remember in a few month's time.
seanmonstar
Jun 29, 2015
Author
Member
Seems obvious to me, since token is now ambiguous, more specific names were needed.
Seems obvious to me, since token is now ambiguous, more specific names were needed.
|
@seanmonstar my last-minute nits notwithstanding, I think we're solid here. Let's land this as soon as train-40 has been cut, and maximise testing time in the wild. r+ \o/ |
See docs/api.md for changes to endpoints. Closes #209
feat(refresh_tokens): add refresh_tokens to /token endpoint
All good to go-ish.
Any test cases I missed with scopes?
Fixes #209