Skip to content

Commit 82091cd

Browse files
authored
Application Identity (Development->Master) (#126)
* Application identity (#125) * Adds support for application identity (app to app flow) * Adds test cases for application identity * Adds documentation on how to use App to App flow * Bump up version number to 4.2.0
1 parent e971c45 commit 82091cd

File tree

5 files changed

+152
-16
lines changed

5 files changed

+152
-16
lines changed

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,45 @@ To utilize the custom identity flow, the user must first register a public key i
243243

244244
Refer to the [documentation on custom identity](https://console.bluemix.net/docs/services/appid/custom.html#custom-identity) for more details on how to implement App ID's custom identity flow in your application.
245245

246+
247+
### Application Identity
248+
249+
In case you want to invoke protected/secure APIs from applications or clients that are non user interactive, you can use the App ID app to app flow to authenticate and authorize your non user interactive applications.
250+
251+
App ID app to app flow implements the OAuth2.0 Client Credentials grant.
252+
253+
Before you can obtain access tokens for the app to app flow, you need to obtain a `client ID` and a `secret` by registering your application with your App ID instance. Refer to the [App ID app to app documentation](https://console.bluemix.net/docs/services/appid/app-to-app.html#registering) on how to register your applications.
254+
255+
Since the application needs to store the `client ID` and the `secret`, the app to app flow must never be used with untrusted clients such as mobile clients and browser based applications.
256+
257+
Also, note that this flow only returns an access token and no identity or refresh tokens are issued in this flow.
258+
259+
The code snippet below describes how to obtain the access tokens for the app to app flow.
260+
261+
```javascript
262+
const config = {
263+
tenantId: "{tenant-id}",
264+
clientId: "{client-id}",
265+
secret: "{secret}",
266+
oauthServerUrl: "{oauth-server-url}"
267+
};
268+
269+
const TokenManager = require('ibmcloud-appid').TokenManager;
270+
271+
async function getAppIdentityToken() {
272+
try {
273+
const tokenResponse = await tokenManager.getApplicationIdentityToken();
274+
console.log('Token response : ' + JSON.stringify(tokenResponse));
275+
276+
//the token response contains the access_token, expires_in, token_type
277+
} catch (err) {
278+
console.log('err obtained : ' + err);
279+
res.status(500).send(err.toString());
280+
}
281+
}
282+
```
283+
For more detailed information on using app to app flow, refer to the [App ID documentation](https://console.bluemix.net/docs/services/appid/app-to-app.html#app).
284+
246285
### Manage User Profile
247286
Using the App ID UserProfileManager, you are able to create, delete, and retrieve user profile attributes as well as get additional info about a user.
248287

@@ -469,7 +508,7 @@ Gets the stored details of the Cloud directory user.
469508
uuid is the Cloud Directory user uuid.
470509

471510
```javascript
472-
selfServiceManager.getUserDetails(uuid, iamToke).then(function (user) {
511+
selfServiceManager.getUserDetails(uuid, iamToken).then(function (user) {
473512
logger.debug('user details:' + JSON.stringify(user));
474513
}).catch(function (err) {
475514
logger.error(err);

lib/token-manager/token-manager.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ const TokenUtil = require("../utils/token-util");
2020
const ServiceUtil = require('../utils/service-util');
2121
const PublicKeyUtil = require("../utils/public-key-util");
2222

23-
const TOKEN_PATH = '/token';
24-
const CUSTOM_IDENTITY_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
2523

2624
function TokenManager(options) {
2725
logger.debug("Initializing token manager");
@@ -37,10 +35,10 @@ TokenManager.prototype.getCustomIdentityTokens = function (jwsToken, scopes = []
3735
const clientId = this.serviceConfig.getClientId();
3836
const secret = this.serviceConfig.getSecret();
3937
const scope = scopes.join(' ');
40-
const tokenEndpoint = this.serviceConfig.getOAuthServerUrl() + TOKEN_PATH;
38+
const tokenEndpoint = this.serviceConfig.getOAuthServerUrl() + constants.TOKEN_PATH;
4139

4240

43-
return getTokens(tokenEndpoint, clientId, secret, CUSTOM_IDENTITY_GRANT_TYPE, scope, jwsToken)
41+
return getTokens(tokenEndpoint, clientId, secret, constants.CUSTOM_IDENTITY_GRANT_TYPE, scope, jwsToken)
4442
.then((tokenResponse) => {
4543
if (!tokenResponse) {
4644
logger.error('Unable to parse token response');
@@ -64,6 +62,32 @@ TokenManager.prototype.getCustomIdentityTokens = function (jwsToken, scopes = []
6462
});
6563
};
6664

65+
TokenManager.prototype.getApplicationIdentityToken = function () {
66+
67+
const clientId = this.serviceConfig.getClientId();
68+
const secret = this.serviceConfig.getSecret();
69+
const tokenEndPoint = this.serviceConfig.getOAuthServerUrl() + constants.TOKEN_PATH;
70+
71+
return getTokens(tokenEndPoint, clientId, secret, constants.APP_TO_APP_GRANT_TYPE)
72+
.then((tokenResponse) => {
73+
if(!tokenResponse) {
74+
logger.error('Unable to parse token response');
75+
throw Error('Unable to parse token response');
76+
}
77+
78+
const accessToken = tokenResponse['access_token'];
79+
const tokenType = tokenResponse['token_type'];
80+
const expiresIn = tokenResponse['expires_in'];
81+
82+
return validateToken('access', accessToken, this.serviceConfig)
83+
.then(() => ({
84+
accessToken,
85+
tokenType,
86+
expiresIn
87+
}));
88+
});
89+
};
90+
6791
function getTokens(tokenEndpoint, clientId, secret, grant_type, scope, assertion) {
6892
return new Promise((resolve, reject) => {
6993
request({

lib/utils/constants.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ module.exports = {
1111
REDIRECT_URI: "redirectUri",
1212
PREFERRED_LOCALE: "preferredLocale",
1313
ISS: "iss",
14-
AUD: "aud"
14+
AUD: "aud",
15+
CUSTOM_IDENTITY_GRANT_TYPE: "urn:ietf:params:oauth:grant-type:jwt-bearer",
16+
APP_TO_APP_GRANT_TYPE: "client_credentials",
17+
TOKEN_PATH: "/token"
1518
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ibmcloud-appid",
3-
"version": "4.1.1",
3+
"version": "4.2.0",
44
"description": "Node.js SDK for the IBM Cloud App ID service",
55
"main": "lib/appid-sdk.js",
66
"scripts": {

test/token-manager-test.js

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ const UNAUTHORIZED = 'return_code:401';
3636
const NOT_FOUND = 'return_code:404';
3737
const SERVER_ERROR = 'return_code:500';
3838

39+
const CUSTOM = 'CUSTOM';
40+
const APP_TO_APP = 'APP2APP';
41+
3942
const mockConfig = (event) => ({
4043
tenantId: 'test-tenant-id',
4144
clientId: 'test-client-id',
@@ -83,14 +86,37 @@ function mockRequest(options, callback) {
8386
}
8487
}
8588

86-
function mockRetrieveTokenFailure(tokenManager, expectedErrMessage, done) {
87-
tokenManager.getCustomIdentityTokens(mockJwsToken)
89+
function mockRetrieveTokenFailure(tokenManager, grantType, expectedErrMessage, done) {
90+
91+
let params = [];
92+
let funcToTest;
93+
switch (grantType) {
94+
case CUSTOM : {
95+
params.push(mockJwsToken);
96+
funcToTest = tokenManager.getCustomIdentityTokens;
97+
break;
98+
}
99+
100+
case APP_TO_APP : {
101+
funcToTest = tokenManager.getApplicationIdentityToken;
102+
break;
103+
}
104+
105+
default: {
106+
throw Error('Invalid function to test');
107+
}
108+
109+
}
110+
111+
funcToTest.apply(tokenManager, params)
88112
.catch((error) => {
89113
assert.equal(error.message, expectedErrMessage);
90114
done();
91115
});
116+
92117
}
93118

119+
94120
describe('/lib/token-manager/token-manager', () => {
95121
let TokenManager;
96122

@@ -104,32 +130,32 @@ describe('/lib/token-manager/token-manager', () => {
104130
describe('#TokenManager.getCustomIdentityTokens', () => {
105131
it('Should fail access token validation', function (done) {
106132
const tokenManager = new TokenManager(mockConfig(INVALID_ACCESS_TOKEN));
107-
mockRetrieveTokenFailure(tokenManager, 'Invalid access token', done);
133+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Invalid access token', done);
108134
});
109135

110136
it('Should fail identity token validation', function (done) {
111137
const tokenManager = new TokenManager(mockConfig(INVALID_IDENTITY_TOKEN));
112-
mockRetrieveTokenFailure(tokenManager, 'Invalid identity token', done);
138+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Invalid identity token', done);
113139
});
114140

115141
it('Should fail to retrieve tokens - 400', function (done) {
116142
const tokenManager = new TokenManager(mockConfig(BAD_REQUEST));
117-
mockRetrieveTokenFailure(tokenManager, 'Failed to obtain tokens', done);
143+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Failed to obtain tokens', done);
118144
});
119145

120146
it('Should not retrieve tokens - 401', function (done) {
121147
const tokenManager = new TokenManager(mockConfig(UNAUTHORIZED));
122-
mockRetrieveTokenFailure(tokenManager, 'Unauthorized', done)
148+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Unauthorized', done)
123149
});
124150

125151
it('Should not retrieve tokens - 404', function (done) {
126152
const tokenManager = new TokenManager(mockConfig(NOT_FOUND));
127-
mockRetrieveTokenFailure(tokenManager, 'Not found', done);
153+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Not found', done);
128154
});
129155

130156
it('Should not retrieve tokens - 500', function (done) {
131157
const tokenManager = new TokenManager(mockConfig(SERVER_ERROR));
132-
mockRetrieveTokenFailure(tokenManager, 'Unexpected error', done);
158+
mockRetrieveTokenFailure(tokenManager, CUSTOM, 'Unexpected error', done);
133159
});
134160

135161
it('Should retrieve tokens - Happy Flow', (done) => {
@@ -145,4 +171,48 @@ describe('/lib/token-manager/token-manager', () => {
145171
.catch((error) => done(error));
146172
});
147173
});
148-
});
174+
175+
176+
describe('#TokenManager.getAppToAppToken', () => {
177+
178+
it('Should fail token validation - wrong tenant', function(done) {
179+
const tokenManager = new TokenManager(mockConfig(INVALID_ACCESS_TOKEN));
180+
mockRetrieveTokenFailure(tokenManager, APP_TO_APP, 'Invalid access token', done);
181+
});
182+
183+
it('Should not retrieve tokens - 404', function(done) {
184+
const tokenManager = new TokenManager(mockConfig(NOT_FOUND));
185+
mockRetrieveTokenFailure(tokenManager, APP_TO_APP, 'Not found', done);
186+
187+
});
188+
189+
it('Should not retrieve tokens - 401', function(done) {
190+
const tokenManager = new TokenManager(mockConfig(UNAUTHORIZED));
191+
mockRetrieveTokenFailure(tokenManager, APP_TO_APP, 'Unauthorized', done)
192+
});
193+
194+
it('Should not retrieve tokens - 400', function(done) {
195+
const tokenManager = new TokenManager(mockConfig(BAD_REQUEST));
196+
mockRetrieveTokenFailure(tokenManager, APP_TO_APP, 'Failed to obtain tokens', done);
197+
});
198+
199+
it('Should not retrieve tokens - 500', function(done) {
200+
const tokenManager = new TokenManager(mockConfig(SERVER_ERROR));
201+
mockRetrieveTokenFailure(tokenManager, APP_TO_APP, 'Unexpected error', done);
202+
});
203+
204+
it('should retrieve tokens - Happy Flow', function(done) {
205+
206+
const tokenManager = new TokenManager(mockConfig(SUCCESS));
207+
tokenManager.getApplicationIdentityToken().then((context) => {
208+
assert.equal(context.accessToken, mockAccessToken)
209+
assert.equal(context.expiresIn, 9999999999)
210+
assert.equal(context.tokenType, 'Bearer')
211+
done()
212+
}).catch ((err) => {
213+
done(err);
214+
})
215+
});
216+
});
217+
218+
});

0 commit comments

Comments
 (0)