diff --git a/OAuth2.agent.lib.nut b/OAuth2.agent.lib.nut index 2dcd6f9..75f66b9 100644 --- a/OAuth2.agent.lib.nut +++ b/OAuth2.agent.lib.nut @@ -40,7 +40,7 @@ enum Oauth2DeviceFlowState { // The class that introduces OAuth2 namespace class OAuth2 { - static VERSION = "1.0.0"; + static VERSION = "2.0.0"; } // The class that represents OAuth 2.0 authorization flow @@ -79,7 +79,7 @@ class OAuth2.JWTProfile { // Parameters: // provider OAuth2 provider configuration // Must be a table with following set of strings: - // TOKEN_HOST - provider's token endpoint URI + // tokenHost - provider's token endpoint URI // params Client specific parameters // Must be a table with following set of strings: // iss - JWT issuer @@ -89,10 +89,10 @@ class OAuth2.JWTProfile { // https://github.com/electricimp/AWSLambda/blob/master/examples/RSACrypto#setting-up-the-aim-user // sub - [optional] the subject of the JWT constructor(provider, user) { - if (!("TOKEN_HOST" in provider) ) { + if (!("tokenHost" in provider) ) { throw "Invalid Provider"; } - _tokenHost = provider.TOKEN_HOST; + _tokenHost = provider.tokenHost; if (!("iss" in user) || !("scope" in user) || @@ -104,8 +104,11 @@ class OAuth2.JWTProfile { _iss = user.iss; // mandatory field but GOOGLE skips it - if ("sub" in user) _sub = user.sub; - else _sub = _iss; + if ("sub" in user) { + _sub = user.sub; + } else { + _sub = _iss; + } _scope = user.scope; _jwtSignKey = user.jwtSignKey; @@ -116,7 +119,7 @@ class OAuth2.JWTProfile { // Returns: // Access token as string object // Null if the client is not authorized or token is expired - function getValidAccessTokeOrNull() { + function getValidAccessTokenOrNull() { if (isTokenValid()) { return _accessToken; } else { @@ -170,7 +173,7 @@ class OAuth2.JWTProfile { "message" : header + "." + body }; - _log("Calling lambda:" + signrequest); + _log("Calling lambda..."); _signer.invoke({ "payload" : signrequest, "functionName" : "RSALambda" @@ -277,13 +280,13 @@ class OAuth2.JWTProfile { // Records non-error event function _log(message) { if (_debug) { - server.log("[OAuth2JWTProfile]" + message); + server.log("[OAuth2JWTProfile] " + message); } } // Records error event function _error(message) { - server.error("[OAuth2JWTProfile]" + message); + server.error("[OAuth2JWTProfile] " + message); } } @@ -296,9 +299,9 @@ class OAuth2.DeviceFlow { // Predefined configuration for Google Authorization service GOOGLE = { - "LOGIN_HOST" : "https://accounts.google.com/o/oauth2/device/code", - "TOKEN_HOST" : "https://www.googleapis.com/oauth2/v4/token", - "GRANT_TYPE" : "http://oauth.net/grant_type/device/1.0", + "loginHost" : "https://accounts.google.com/o/oauth2/device/code", + "tokenHost" : "https://www.googleapis.com/oauth2/v4/token", + "grantType" : "http://oauth.net/grant_type/device/1.0", }; // The class that represents OAuth2 Client role. @@ -349,23 +352,23 @@ class OAuth2.DeviceFlow { // Parameters: // provider OAuth2 provider configuration // Must be a table with following set of strings: - // LOGIN_HOST - provider's device authorization endpoint URI - // TOKEN_HOST - provider's token endpoint URI - // GRANT_TYPE - [optional] grant type + // loginHost - provider's device authorization endpoint URI + // tokenHost - provider's token endpoint URI + // grantType - [optional] grant type // params Client specific parameters // Must be a table with following set of strings: // clientId - client identifier // scope - authorization scope // clientSecret- [optional] client secret (password) constructor(provider, params) { - if ( !("LOGIN_HOST" in provider) || - !("TOKEN_HOST" in provider) ) { + if ( !("loginHost" in provider) || + !("tokenHost" in provider) ) { throw "Invalid Provider"; } - _loginHost = provider.LOGIN_HOST; - _tokenHost = provider.TOKEN_HOST; + _loginHost = provider.loginHost; + _tokenHost = provider.tokenHost; - if ("GRANT_TYPE" in provider) _grantType = provider.GRANT_TYPE; + if ("grantType" in provider) _grantType = provider.grantType; if (!("clientId" in params) || !("scope" in params)) throw "Invalid Config"; @@ -380,7 +383,7 @@ class OAuth2.DeviceFlow { // Returns: // Access token as string object // Null if the client is not authorized or token is expired - function getValidAccessTokeOrNull() { + function getValidAccessTokenOrNull() { if (isAuthorized() && isTokenValid()) { return _accessToken; } else { @@ -808,12 +811,14 @@ class OAuth2.DeviceFlow { // Records error event function _error(txt) { - server.error(txt); + server.error("[OAuth2DeviceFlow] " + txt); } // Records non-error event function _log(txt) { - if (_debug) server.log(txt); + if (_debug) { + server.log("[OAuth2DeviceFlow] " + txt); + } } } // end of Client } \ No newline at end of file diff --git a/README.md b/README.md index f619876..8f236e4 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,41 @@ # OAuth 2.0 -OAuth 2.0 authentication and authorization flows implementation. The library supports -the following flows: -- [OAuth2.JWTProfile.Client](#oauth2jwtprofileclient) — OAuth 2.0 with JSON Web Token (JWT) Profile for Client Authentication and Authorization Grants - defined in the [IETF RFC 7523](https://tools.ietf.org/html/rfc7523). -- [OAuth2.DeviceFlow.Client](#oauth2deviceflowclient) — Device Flow for browserless and input constrained devices. The implementation conforms -to the [draft specification](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05). +This library provides OAuth 2.0 authentication and authorization flows. It supports the following flows: -The library exposes access token for applications and hides provider specific -operations including refresh token management and expired access token renewal. +- [OAuth2.JWTProfile.Client](#oauth2jwtprofileclient) — OAuth 2.0 with the JSON Web Token (JWT) Profile for Client Authentication and Authorization Grants as defined in [IETF RFC 7523](https://tools.ietf.org/html/rfc7523). +- [OAuth2.DeviceFlow.Client](#oauth2deviceflowclient) — OAuth 2.0 Device Flow for browserless and input-constrained devices. The implementation conforms to the [IETF draft device flow specification](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05). -**To add this library to your project, add** `#require "OAuth2.agent.lib.nut:1.0.0"` **to the top of your agent code.** +The library exposes retrieved access tokens for applications and hides provider-specific operations, including the renewal of expired tokens. + +**To add this library to your project, add** `#require "OAuth2.agent.lib.nut:2.0.0"` **to the top of your agent code.** ## OAuth2.JWTProfile.Client -The class implements OAuth 2.0 flow with JSON Web Token (JWT) Bearer Token as a means for requesting -an access token as well as for client authentication. +This class implements an OAuth 2.0 client flow using a JSON Web Token (JWT) as the means for requesting access tokens and for client authentication. -**NOTE:** The flow requires RSA SHA256 signature, which is not currently supported by the Electric Imp -[Agent API](https://electricimp.com/docs/api/agent/). As a temporary solution it is proposed to use -[AWS Lambda](https://aws.amazon.com/lambda) function that will do -[RSA-SHA256 signatures](examples#amazon-lambda-for-rsa-sha256-signatures) for an agent. -AWS Lambda is subject to a service charge (please refer to Amazon pricing -[page](https://aws.amazon.com/lambda/pricing/) for more details). +**Note** The flow requires an RSA-SHA256 signature which is not currently supported by the Electric Imp [imp API](https://electricimp.com/docs/api/). As a temporary solution we suggest that you use an [AWS Lambda](https://aws.amazon.com/lambda) function that will perform [RSA-SHA256 signatures](examples#amazon-lambda-for-rsa-sha256-signatures) for an agent. However, please note that AWS Lambda is subject to a service charge so you should refer to the Amazon pricing [page](https://aws.amazon.com/lambda/pricing/) for more information before proceeding. -### constructor(providerSettings, userSettings) +## OAuth2.JWTProfile.Client Usage -Construction that creates an instance of the `OAuth2.JWTProfile.Client`. +### constructor(*providerSettings, userSettings*) -The first parameter `providerSettings` is a map that contains provider specific settings: +The constructor creates an instance of the *OAuth2.JWTProfile.Client* class. The first parameter, *providerSettings*, is a table that contains provider-specific settings: | Parameter | Type | Use | Description | | --- | --- | --- | --- | -| `TOKEN_HOST` | *string* | Required | Token endpoint - used by the client to exchange an authorization grant for an access token, typically with client authentication. | +| *tokenHost* | String | Required | The token endpoint. This is used by the client to exchange an authorization grant for an access token, typically with client authentication | -The second parameter `userSettings` defines a map with user and application specific settings: +The second parameter, *userSettings*, defines a table with user- and application-specific settings: | Parameter | Type | Use | Description | | --- | --- | --- | --- | -| `iss` | *string* | Required | JWT issuer | -| `scope` | *string* | Required | Scopes enable your application to only request access to the resources that it needs while also enabling users to control the amount of access that they grant to your application | -| `jwtSignKey` | *string* | Required | JWT sign secret key | -| `rs256signer` | *[AWSLambda](https://github.com/electricimp/awslambda)* | Required | Instance of [AWSLambda](https://github.com/electricimp/awslambda) for RSA-SHA256 encryption. You can use [example](examples#jwt-profile-for-oauth-20) code to create the AWS Lambda function. | -| `sub` | *string* | Optional. *Default:* the value of `iss` | The *subject* of the JWT. Google seems to ignor this field. | - -*Note* Optional `sub` property is substituted by mandatory `iss` property when omitted. +| *iss* | String | Required | The JWT issuer | +| *scope* | String | Required | A scope. Scopes enable your application to request access only to the resources that it needs while also enabling users to control the amount of access that they grant to your application | +| *jwtSignKey* | String | Required | A JWT sign secret key | +| *rs256signer* | *[AWSLambda](https://github.com/electricimp/awslambda)* | Required | Instance of [AWSLambda](https://github.com/electricimp/awslambda) for RSA-SHA256 encryption. You can use [this example code](examples#jwt-profile-for-oauth-20) to create the AWS Lambda function | +| *sub* | String | Optional. *Default:* the value of *iss* | The subject of the JWT. **Note** Google seems to ignore this field | +**Note** When omitted, the optional *sub* property is substituted by the mandatory *iss* property. #### JWT Profile Client Creation Example @@ -55,7 +45,7 @@ The second parameter `userSettings` defines a map with user and application spec #require "AWSLambda.agent.lib.nut:1.0.0" // OAuth 2.0 library -#require "OAuth2.agent.lib.nut:1.0.0" +#require "OAuth2.agent.lib.nut:2.0.0" // Substitute with real values const LAMBDA_REGION = "us-west-1"; @@ -68,8 +58,9 @@ const GOOGLE_SECRET_KEY = "-----BEGIN PRIVATE KEY-----\nprivate key goes here local signer = AWSLambda(LAMBDA_REGION, LAMBDA_ACCESS_KEY_ID, LAMBDA_ACCESS_KEY); local providerSettings = { - "TOKEN_HOST" : "https://www.googleapis.com/oauth2/v4/token" + "tokenHost" : "https://www.googleapis.com/oauth2/v4/token" }; + local userSettings = { "iss" : GOOGLE_ISS, "jwtSignKey" : GOOGLE_SECRET_KEY, @@ -79,88 +70,82 @@ local userSettings = { local client = OAuth2.JWTProfile.Client(providerSettings, userSettings); ``` -**IMPORTANT:** The name of the AWS Lambda function must be `RSALambda`! -### acquireAccessToken(tokenReadyCallback) +**Important** The name of the AWS Lambda function **must** be `RSALambda`. -Starts access token acquisition procedure. Invokes the provided callback function immediately -if access token is available and valid. +## OAuth2.JWTProfile.Client Methods -Parameter details: +### acquireAccessToken(*tokenReadyCallback*) -| Parameter | Type | Use | Description | -| --- | --- | --- | --- | -| `tokenReadyCallback` | Function | Required | The handler to be called when access token is acquired or an error occurs | +This method begins the access-token acquisition procedure. It invokes the provided callback function immediately if the access token is already available and valid. -`tokenReadyCallback` callback should have two parameters: +The function passed into *tokenReadyCallback* should have two parameters of its own: | Parameter | Type | Description | | --- | --- | --- | -| `token` | *string* | String representation of access token | -| `error` | *string* | String with error details, `null` in case of success | +| *token* | String | The access token | +| *error* | String | Error details, or `null` in the case of success | #### Example -Using `client` from previous [sample](#jwt-profile-client-creation-example) - ```squirrel client.acquireAccessToken( - function(resp, err) { - server.log(resp); - if (err) { - server.error(err); + function(token, error) { + if (error) { + server.error(error); + } else { + server.log("The access token has the value: " + token); } } ); ``` -### getValidAccessTokeOrNull() -Returns access token string non blocking way. Returns access token as a string object if token is valid, -null if the client is not authorized or token is expired. +### getValidAccessTokenOrNull() -#### Example +This method returns an access token string in a non-blocking way. It returns `null` if the client is not authorized or the token has expired. -Using `client` from the first [sample](#jwt-profile-client-creation-example) +#### Example ```squirrel -local token = client.getValidAccessTokeOrNull(); -if (token) server.log("token is valid and has value: " + token); -else server.log("token is either expired or client is not authorized!"); +local token = client.getValidAccessTokenOrNull(); + +if (token) { + server.log("The access token is valid and has the value: " + token); +} else { + server.log("The access token has either expired or the client is not authorized"); +} ``` ### isTokenValid() -Checks if access token is valid by comparing its expire time with current one. +This method checks if the access token is valid by comparing its expiry time with current time. It returns a Boolean value: `true` if the token is valid, or `false` if the token has expired. #### Example -Using `client` from the first [sample](#jwt-profile-client-creation-example) - ```squirrel -server.log("token is valid=" + client.isTokenValid()); +server.log("The access token is " + (client.isTokenValid() ? "valid" : "invalid")); ``` -## Complete usage sample - -To connect all the parts together and show a sample of common case of library usage let's take a look a following sample +## Complete Example ```squirrel #require "AWSRequestV4.class.nut:1.0.2" #require "AWSLambda.agent.lib.nut:1.0.0" -#require "OAuth2.agent.lib.nut:1.0.0 +#require "OAuth2.agent.lib.nut:2.0.0 // Substitute with real values const LAMBDA_REGION = "us-west-1"; -const LAMBDA_ACCESS_KEY_ID = ""; -const LAMBDA_ACCESS_KEY = ""; +const LAMBDA_ACCESS_KEY_ID = ""; +const LAMBDA_ACCESS_KEY = ""; const GOOGLE_ISS = "rsalambda@quick-cacao-168121.iam.gserviceaccount.com"; const GOOGLE_SECRET_KEY = "-----BEGIN PRIVATE KEY-----\nprivate key goes here\n-----END PRIVATE KEY-----\n"; local signer = AWSLambda(LAMBDA_REGION, LAMBDA_ACCESS_KEY_ID, LAMBDA_ACCESS_KEY); local providerSettings = { - "TOKEN_HOST" : "https://www.googleapis.com/oauth2/v4/token" + "tokenHost" : "https://www.googleapis.com/oauth2/v4/token" }; + local userSettings = { "iss" : GOOGLE_ISS, "jwtSignKey" : GOOGLE_SECRET_KEY, @@ -170,122 +155,111 @@ local userSettings = { local client = OAuth2.JWTProfile.Client(providerSettings, userSettings); -local token = client.getValidAccessTokeOrNull(); +local token = client.getValidAccessTokenOrNull(); if (token != null) { + // We have a valid token already server.log("Valid access token is: " + token); } else { - // Starting procedure of access token acquisition - local error = client.acquireAccessToken( - function(resp, err) { + // Acquire a new access token + client.acquireAccessToken( + function(newToken, err) { if (err) { server.error("Token acquisition error: " + err); } else { - server.log("Received token: " + resp); + server.log("Received a new token: " + newToken); } } ); - - if (null != error) server.error("Failed to obtain token: " + error); } ``` -**NOTE:** JWT Profile for OAuth 2.0 was verified and tested with -Google [PubSub](https://cloud.google.com/pubsub/docs/) authorization flow. - +**Note** The JSON Web Token (JWT) Profile for OAuth 2.0 was verified and tested with the Google [PubSub](https://cloud.google.com/pubsub/docs/) authorization flow. ## OAuth2.DeviceFlow.Client -The class implements OAuth 2.0 authorization flow for browserless and input -constrained devices, often referred to as the -[device flow](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05), enables -OAuth clients to request user authorization from devices that have an -Internet connection, but don't have an easy input method, or lack a -suitable browser for a more traditional OAuth flow. This -authorization flow instructs the user to perform the authorization -request on a secondary device, such as a smartphone. - +This class implements an OAuth 2.0 authorization flow for browserless and/or input-constrained devices. Often referred to as the [device flow](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05), this flow enables OAuth clients to request user authorization from devices that have an Internet connection but lack a suitable input method or web browser required for a more traditional OAuth flow. This authorization flow therefore instructs the user to perform the authorization request on a secondary device, such as a smartphone. -### constructor(providerSettings, userSettings) +## OAuth2.DeviceFlow.Client Usage -Construction that creates an instance of the `OAuth2.DeviceFlow.Client`. +### constructor(*providerSettings, userSettings*) -The first parameter `providerSettings` is a map that contains provider specific settings: +This constructor creates an instance of the *OAuth2.DeviceFlow.Client* class. The first parameter, *providerSettings*, is a table that contains provider-specific settings: | Parameter | Type | Use | Description | | --- | --- | --- | --- | -| `LOGIN_HOST` | *string* | Required | Authorization endpoint - used by the client to obtain authorization from the resource owner via user-agent redirection. authorization server | -| `TOKEN_HOST` | *string* | Required | Token endpoint - used by the client to exchange an authorization grant for an access token, typically with client authentication. | -| `GRANT_TYPE` | *string* | Optional. *Default:* `urn:ietf:params:oauth:grant-type:device_code` | Grant type identifier supported by the provider | +| *loginHost* | String | Required | The authorization endpoint. This is used by the client to obtain authorization from the resource owner via user-agent redirection | +| *tokenHost* | String | Required | The token endpoint. This is used by the client to exchange an authorization grant for an access token, typically with client authentication | +| *grantType* | String | Optional. Default: `"urn:ietf:params:oauth:grant-type:device_code"` | The grant type identifier supported by the provider | -The second parameter `userSettings` defines a map with user and application specific settings: +The second parameter, *userSettings*, takes a table containing user- and application-specific settings: | Parameter | Type | Use |Description | | --- | --- | --- | --- | -| `clientId` | *string* | Required | OAuth client ID | -| `clientSecret` | *string* | Required | The project's client secret | -| `scope` | *string* | Required | Scopes enable your application to only request access to the resources that it needs while also enabling users to control the amount of access that they grant to your application. | +| *clientId* | String | Required | The OAuth client ID | +| *clientSecret* | String | Required | The project's client secret | +| *scope* | String | Required | A scope. Scopes enable your application to only request access to the resources that it needs while also enabling users to control the amount of access that they grant to your application | -The library provides predefined configuration settings for -Google Device Auth flow. These settings are defined in the provider -specific settings map:`OAuth2.DeviceFlow.GOOGLE`. The table -provides `LOGIN_HOST`, `TOKEN_HOST` and `GRANT_TYPE` values. +The library provides predefined configuration settings for the Google Device Auth flow. These settings are defined in the provider-specific settings map: *OAuth2.DeviceFlow.GOOGLE*. This table provides pre-populated *loginHost, tokenHost* and *grantType* values. #### Device Flow Client Creation Example ```squirrel - local providerSettings = { - "LOGIN_HOST" : "https://accounts.google.com/o/oauth2/device/code", - "TOKEN_HOST" : "https://www.googleapis.com/oauth2/v4/token", - "GRANT_TYPE" : "http://oauth.net/grant_type/device/1.0", - }; - local userSettings = { - "clientId" : "USER_FIREBASE_CLIENT_ID", - "clientSecret" : "USER_FIREBASE_CLIENT_SECRET", - "scope" : "email profile", - }; - - client <- OAuth2.DeviceFlow.Client(providerSettings, userSettings); +local providerSettings = { + "loginHost" : "https://accounts.google.com/o/oauth2/device/code", + "tokenHost" : "https://www.googleapis.com/oauth2/v4/token", + "grantType" : "http://oauth.net/grant_type/device/1.0", +}; + +local userSettings = { + "clientId" : "", + "clientSecret" : "", + "scope" : "email profile", +}; + +client <- OAuth2.DeviceFlow.Client(providerSettings, userSettings); ``` -### acquireAccessToken(tokenReadyCallback, notifyUserCallback, force) +## OAuth2.DeviceFlow.Client Methods -Starts access token acquisition procedure. Depending on Client state may starts full client authorization procedure or -just token refreshing. Returns null in case of success and error otherwise. Access token is delivered through provided *tokenReadyCallback* function. +### acquireAccessToken(*tokenReadyCallback, notifyUserCallback, force*) + +This method begins the access-token acquisition procedure. Depending on the client state, it may start a full client authorization procedure or just refresh a token that has already been acquired. It returns `null` in the case of success, or an error message if the client is already performing a request and the *force* directive is set. The access token is delivered through the function passed into the *tokenReadyCallback* function. Parameter details: | Parameter | Type | Use | Description | | --- | --- | --- | --- | -| `tokenReadyCallback` | *function* | Required | The handler to be called when access token is acquired or an error occurred | -| `notifyUserCallback` | *function* | Required | The handler to be called when user action is required. See [RFE, device flow, section3.3](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05#section-3.3) | -| `force` | *boolean* | Optional. *Default:* `false` | The flag forces the token acquisition process to start from the beginning even if the previous request did not complete yet. The previous session will be terminated. | +| *tokenReadyCallback* | Function | Required | The handler that will be called when the access token has been acquired, or an error has occurred. The function’s parameters are described below | +| *notifyUserCallback* | Function | Required | The handler that will be called when user action is required. See [RFE, device flow, section 3.3](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-05#section-3.3) for information on what user action might be needed when this callback is executed. The function’s parameters are described below | +| *force* | Boolean | Optional. Default: `false` | This flag forces the token acquisition process to start from the beginning even if a previous request has not yet completed. Any previous session will be terminated | -where `tokenReadyCallback` should have the following parameters: +The *tokenReadyCallback* function should have the following parameters: | Parameter | Type | Description | | --- | --- | --- | -| `token` | *string* | String representation of access token | -| `error` | *string* | String with error details, `null` in case of success | +| *token* | String | String representation of the access token | +| *error* | String | Error details, or `null` in the case of success | -and `notifyUserCallback` should have two parameters: +The *notifyUserCallback* function should have the following parameters: | Parameter | Type | Description | | --- | --- | --- | -| `uri` | *string* | The URI the user need to use for client authorization | -| `code` | *string* | The code for the authorization server | +| *url* | String | The URL the user needs to use for client authorization | +| *code* | String | The code for the authorization server | #### Example -Using `client` from previous [sample](#device-flow-client-creation-example) - ```squirrel client.acquireAccessToken( - function(resp, err) { - server.log(resp); + // Token Ready Callback + function(token, err) { if (err) { - server.error(err); + server.error("Token retrieval error: " + err); + } else { + server.log("The access token: " + token); } }, + // User notification callback function(url, code) { server.log("Authorization is pending. Please grant access."); server.log("URL: " + url); @@ -293,17 +267,16 @@ client.acquireAccessToken( } ); ``` -### getValidAccessTokeOrNull() -Immediately returns either existing access token if it's valid, or null if it expired or -the client is not authorized yet. +### getValidAccessTokenOrNull() -#### Example +This method immediately returns either an existing access token if it is valid, or `null` if the token has expired or the client is yet not authorized. -Using `client` from the first [sample](#device-flow-client-creation-example) +#### Example ```squirrel -local token = client.getValidAccessTokeOrNull(); +local token = client.getValidAccessTokenOrNull(); + if (token) { server.log("Token is valid: " + token); } else { @@ -313,75 +286,70 @@ if (token) { ### isTokenValid() -Checks if access token is valid. +This method checks if the current access token is valid. It returns `true` if this the case, or `false` if the token is no longer valid. #### Example -Using `client` from the first [sample](#device-flow-client-creation-example) - ```squirrel -server.log("Token is valid: " + client.isTokenValid()); +server.log("The access token is " + (client.isTokenValid() ? "valid" : "invalid")); ``` ### isAuthorized() -Checks if the client is authorized and able to refresh expired access token. +This method checks if the client is authorized and able to refresh an expired access token. -Using `client` from the first [sample](#device-flow-client-creation-example) +#### Example ```squirrel -server.log("Client is authorized: " + client.isAuthorized()); +server.log("The client is " + (client.isAuthorized() ? "authorized" : "unauthorized")); ``` -### refreshAccessToken(tokenReadyCallback) - -Asynchronously refreshes access token and invokes `tokenReadyCallback` when done or an error occurs. +### refreshAccessToken(*tokenReadyCallback*) -Function `tokenReadyCallback` should have two parameters: +This method asynchronously refreshes the access token and invokes the function passed into the *tokenReadyCallback* parameter when this has been completed or an error occurs. The *tokenReadyCallback* function has two parameters: | Parameter | Type | Description | | --- | --- | --- | -| token | String | String representation of access token | -| error | String | String with error details, `null` in case of success | +| *token* | String | The access token | +| *error* | String | Error details, or `null` in the case of success | #### Example -Using `client` from the first [sample](#device-flow-client-creation-example) - ```squirrel client.refreshAccessToken( - function(resp, err) { - server.log(resp); + function(token, err) { if (err) { - server.error(err); + server.error("Token refresh error: " + err); + } else { + server.log("The access token is refreshed. It has the value: " + token); } } ); ``` -## Complete usage sample - -To connect all the parts together and show a sample of common case of library usage let's take a look a following sample +## Complete Example ```squirrel -#require "OAuth2.agent.lib.nut:1.0.0 +#require "OAuth2.agent.lib.nut:2.0.0 // Fill CLIENT_ID and CLIENT_SECRET with correct values local userConfig = { - "clientId" : "CLIENT_ID", - "clientSecret" : "CLIENT_SECRET", + "clientId" : "", + "clientSecret" : "", "scope" : "email profile", }; -// Initializing client with provided Google Firebase config +// Initialize client with provided Google Firebase config client <- OAuth2.DeviceFlow.Client(OAuth2.DeviceFlow.GOOGLE, userConfig); -local token = client.getValidAccessTokeOrNull(); +local token = client.getValidAccessTokenOrNull(); + if (token != null) { server.log("Valid access token is: " + token); } else { - // Starting procedure of access token acquisition + // Acquire a new access token local error = client.acquireAccessToken( + // Token received callback function function(resp, err) { if (err) { server.error("Token acquisition error: " + err); @@ -389,21 +357,20 @@ if (token != null) { server.log("Received token: " + resp); } }, + // User notification callback function function(url, code) { server.log("Authorization is pending. Please grant access."); server.log("URL: " + url); server.log("CODE: " + code); } ); - - if (null != error) server.error("Failed to obtain token: " + error); + + if (error != null) server.error("Client is already performing request (" + error + ")"); } ``` -**NOTE:** The DeviceFlow Client was verified and tested on the Google [Firebase](https://firebase.google.com) - authorization flow. - +**Note** The DeviceFlow Client was verified and tested using the Google [Firebase](https://firebase.google.com) authorization flow. # License -The OAuth library is licensed under the [MIT License](LICENSE). \ No newline at end of file +The OAuth library is licensed under the [MIT License](LICENSE). diff --git a/examples/DeviceFlowGoogle.agent.nut b/examples/DeviceFlowGoogle.agent.nut index b51eb9e..23685e3 100644 --- a/examples/DeviceFlowGoogle.agent.nut +++ b/examples/DeviceFlowGoogle.agent.nut @@ -22,7 +22,7 @@ // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -#require "OAuth2.agent.lib.nut:1.0.0" +#require "OAuth2.agent.lib.nut:2.0.0" const CLIENT_ID = ""; const CLIENT_SECRET = ""; @@ -37,7 +37,7 @@ local userConfig = { // Initializing client with provided Google Firebase config client <- OAuth2.DeviceFlow.Client(OAuth2.DeviceFlow.GOOGLE, userConfig); -local token = client.getValidAccessTokeOrNull(); +local token = client.getValidAccessTokenOrNull(); if (token != null) { server.log("Valid access token is: " + token); } else { diff --git a/examples/JWTGooglePubSub.agent.nut b/examples/JWTGooglePubSub.agent.nut index 5f89adf..4d440bb 100644 --- a/examples/JWTGooglePubSub.agent.nut +++ b/examples/JWTGooglePubSub.agent.nut @@ -25,7 +25,7 @@ #require "AWSRequestV4.class.nut:1.0.2" #require "AWSLambda.agent.lib.nut:1.0.0" //@include "../OAuth2.agent.lib.nut" -#require "OAuth2.agent.lib.nut:1.0.0" +#require "OAuth2.agent.lib.nut:2.0.0" const GOOGLE_ISS = ""; const GOOGLE_SECRET_KEY = ""; @@ -44,7 +44,7 @@ local config = { // Initializing client with provided Google Firebase config client <- OAuth2.JWTProfile.Client(OAuth2.DeviceFlow.GOOGLE, config); -local token = client.getValidAccessTokeOrNull(); +local token = client.getValidAccessTokenOrNull(); if (token != null) { server.log("Valid access token is: " + token); } else { diff --git a/tests/DeviceFlowGoogleTestCase.agent.test.nut b/tests/DeviceFlowGoogleTestCase.agent.test.nut index b684185..838227b 100644 --- a/tests/DeviceFlowGoogleTestCase.agent.test.nut +++ b/tests/DeviceFlowGoogleTestCase.agent.test.nut @@ -94,7 +94,7 @@ class DeviceFlowGoogleTestCase extends ImpTestCase { function testRunCommandAsynchronously() { return Promise(function (success, failure) { - local token = auth.getValidAccessTokeOrNull(); + local token = auth.getValidAccessTokenOrNull(); if (null != token) { server.log("VerifyTokenTest: it was not null!. something went wrong!"); checkToken(token, success, failure); diff --git a/tests/GooglePubSubJWTAuth.agent.test.nut b/tests/GooglePubSubJWTAuth.agent.test.nut index 5897783..40fa798 100644 --- a/tests/GooglePubSubJWTAuth.agent.test.nut +++ b/tests/GooglePubSubJWTAuth.agent.test.nut @@ -83,7 +83,7 @@ class GooglePubSubJWTAuth extends ImpTestCase { function testAcquireAndVerifyToken() { return Promise(function (success, failure) { - local token = auth.getValidAccessTokeOrNull(); + local token = auth.getValidAccessTokenOrNull(); if (null != token) { server.log("VerifyTokenTest: it was not null!. something went wrong!"); failure("Initial token is not null");