Skip to content

Instantly share code, notes, and snippets.

@remkohdev
Last active September 26, 2019 22:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save remkohdev/1f4f0bdb66c66fe2c228422b8567b10b to your computer and use it in GitHub Desktop.
Save remkohdev/1f4f0bdb66c66fe2c228422b8567b10b to your computer and use it in GitHub Desktop.
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: myAppIdUsername@email.com'

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":[]}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment