Integrate API Connect with AppID OAuth OIDC Provider for Easy API Security

API Connect is an API Management tool, and it allows integration with an OIDC Provider like AppID with Cloud Directory. In API Connect all you need to do in order to integrate AppID OAuth OIDC proivider, is to add a security definition to your Open API Specification or Swagger version 2, e.g.

securityDefinitions:
  oauth-1:
    type: oauth2
    description: ''
    flow: application
    scopes: {}
    tokenUrl: 'https://my-nodered-instance.region.cf.appdomain.cloud/token'
    x-tokenIntrospect:
      url: 'https://my-nodered-instance.region.cf.appdomain.cloud/introspect'

In the Node-RED workflow, configure the two HTTP Request nodes that call AppID. Set the Basic Authentication username and password to your AppID clientID and clientSecret, and change the tenantid in the URL to your actual AppID tenantid.

The user then passes their user credentials as 'x-introspect-' headers:

$ curl -X GET \
  'https://api.us-south.apiconnect.appdomain.cloud/myorg-dev/sandbox/myapi1?par1=123' \
  -H 'Accept: application/json' \
  -H 'Authorization: Basic ab' \
  -H 'x-introspect-granttype: password' \
  -H 'x-introspect-password: myAppIdPassword123' \
  -H 'x-introspect-username: [email protected]'

The Basic Authentication header cannot be missing in the user's request, because we protected the API with OAuth, but this NodeRED flow does not use the value, because in this workflow we consider API Connect itself the application that owns the clientID and clientSecret. You can choose to let the user override the clientID and clientSecret and use the Authorization header of the calling application instead.

[{"id":"123bde2c.0ee5ea","type":"http in","z":"1b93a20.897555e","name":"","url":"/introspect","method":"post","upload":false,"swaggerDoc":"","x":140,"y":180,"wires":[["631f5d94.d4c6cc"]]},{"id":"6988ae91.cf2668","type":"debug","z":"1b93a20.897555e","name":"log3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":310,"y":100,"wires":[]},{"id":"851da745.013c3","type":"http response","z":"1b93a20.897555e","name":"","statusCode":"","headers":{},"x":1090,"y":180,"wires":[]},{"id":"e451c92d.f61fe8","type":"http request","z":"1b93a20.897555e","name":"","method":"POST","ret":"obj","paytoqs":false,"url":"https://us-south.appid.cloud.ibm.com/oauth/v4/<tenantId>/introspect","tls":"","proxy":"","authType":"basic","x":930,"y":180,"wires":[["851da745.013c3"]]},{"id":"2529ab6.a37b554","type":"debug","z":"1b93a20.897555e","name":"log6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1090,"y":140,"wires":[]},{"id":"98d16c1f.3a29f","type":"http request","z":"1b93a20.897555e","name":"","method":"POST","ret":"obj","paytoqs":false,"url":"https://us-south.appid.cloud.ibm.com/oauth/v4/<tenantId>/token","tls":"","proxy":"","authType":"basic","x":550,"y":180,"wires":[["e57d818b.5115a"]]},{"id":"2905ff3e.d98878","type":"debug","z":"1b93a20.897555e","name":"log5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":710,"y":140,"wires":[]},{"id":"bf6cba9a.677298","type":"function","z":"1b93a20.897555e","name":"fnParseCredentials","func":"let decodedAuth = msg.temp_authheader;\nlet credentials = decodedAuth.split(\":\");\n\nlet username = credentials[0];\nlet password = credentials[1];\nlet granttype = \"password\";\n\n// if x- was used, then \n// username:password should be overwritten\n// by x- values\nlet xUsername = msg.req.headers['x-introspect-username'];\nlet xPassword = msg.req.headers['x-introspect-password'];\nlet xGranttype = msg.req.headers['x-introspect-granttype'];\nif(xUsername){\n    username=xUsername;\n}\nif(xPassword){\n    password=xPassword;\n}\nif(xGranttype){\n    granttype=xGranttype;\n}\nmsg.payload['grant_type']=granttype;\nmsg.payload['username']=username;\nmsg.payload['password']=password;\n\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":180,"wires":[["98d16c1f.3a29f"]]},{"id":"f6e86a34.7e1398","type":"debug","z":"1b93a20.897555e","name":"log4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":530,"y":100,"wires":[]},{"id":"e57d818b.5115a","type":"function","z":"1b93a20.897555e","name":"fnParseToken","func":"let accessToken = msg.payload['access_token'];\nlet idToken = msg.payload['id_token'];\nlet tokenType = msg.payload['token_type'];\nlet expiresIn = msg.payload['expires_in'];\n  \nmsg.payload['token']=idToken;\n\nreturn msg;","outputs":1,"noerr":0,"x":740,"y":180,"wires":[["e451c92d.f61fe8"]]},{"id":"d2d375c8.01c2e","type":"comment","z":"1b93a20.897555e","name":"POST /introspection","info":"Instead only the /introspection endpoint is called, \nso hence I moved the /token request into the \nintrospection workflow","x":150,"y":80,"wires":[]},{"id":"8605db32.01c38","type":"base64","z":"1b93a20.897555e","name":"decodeAuthHeader","action":"b64","property":"temp_authheader","x":370,"y":300,"wires":[["bf6cba9a.677298"]]},{"id":"631f5d94.d4c6cc","type":"function","z":"1b93a20.897555e","name":"fnParseAuthHeader","func":"let basicAuth = msg.req.headers['authorization'];\nlet encodedAuth = basicAuth.replace(\"Basic \",\"\");\nmsg.temp_authheader = encodedAuth;\n\nreturn msg;","outputs":1,"noerr":0,"x":170,"y":300,"wires":[["8605db32.01c38"]]},{"id":"4a303450.4c764c","type":"debug","z":"1b93a20.897555e","name":"log1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":330,"y":340,"wires":[]},{"id":"1e21a84e.2e9738","type":"debug","z":"1b93a20.897555e","name":"log2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":530,"y":340,"wires":[]},{"id":"a6a604c7.8b17d","type":"comment","z":"1b93a20.897555e","name":"Allow Basic Auth with username:password","info":"","x":240,"y":260,"wires":[]},{"id":"810f14fd.ec17c","type":"comment","z":"1b93a20.897555e","name":"x-introspect will overwrite Basic Auth","info":"","x":410,"y":140,"wires":[]}]

Flow Info

Created 5 years, 8 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • comment (x3)
  • debug (x6)
  • function (x3)
  • http in (x1)
  • http request (x2)
  • http response (x1)
Other

Tags

  • oauth
  • oidc
  • apiconnect
  • appid
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option