{"info":{"_postman_id":"2b987c2e-8a5d-4f4b-bca6-adcfb51aa8f5","name":"Third Party Auth Flows in MOO","description":"<html><head></head><body><p>This collection demonstrates the auth flows in MOO that are relevant to third parties who are integrating their application with MOO.</p>\n<p>MOO implements the <a href=\"https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2\">OAuth2 standard</a> for authorization.</p>\n<p>To access protected MOO resources the third party application needs a valid access token. There are two distinct types of access token in MOO:</p>\n<ul>\n<li><p>user specific access tokens</p>\n</li>\n<li><p>client specific access token</p>\n</li>\n</ul>\n<h4 id=\"user-specific-access-tokens\">User specific access tokens</h4>\n<p>User specific access tokens can only (initially) be acquired through user interaction in the browser. The third party app can use the authorization code flow and redirect the user's browser to the MOO Secure token service (STS) to request an authorization code. This code can then be exchanged for an access token (and a refresh token).</p>\n<p>The refresh token enables the app to request new user specific access tokens without requiring further user interaction.</p>\n<p>User specific access tokens are limited to requesting resources that the logged in user has access to and that the third party client has access to.</p>\n<p>The first five requests in this collection illustrate the use of user specific access tokens.</p>\n<h4 id=\"client-specific-access-tokens\">Client specific access tokens</h4>\n<p>Client specific access tokens can be acquired directly by the third party application, and do not require a logged in user.</p>\n<p>Client specific access tokens are limited to requesting resources that the third party client has access to and that support direct client access.</p>\n<p>The last two requests in this collection illustrate the use of client specific access tokens.</p>\n<h4 id=\"scopes\">Scopes</h4>\n<p>For both client and user tokens the application must specify a comma seperated list of scopes in the call to the Authorize or Token endpoints. These scopes indicate the api access that are requested for the token. Clients can only specify scopes that they have explicitly been given access to by MOO. It is also considered best practice to only request the scopes that are required for that given token / context.</p>\n<h4 id=\"testing-with-postman\">Testing with postman</h4>\n<p>If you want to test the calls using postman directly you need to do the following:</p>\n<ul>\n<li><p>Install postman</p>\n</li>\n<li><p>Open <a href=\"https://heutink-ict.atlassian.net/wiki/download/attachments/24477711/Third%20Party%20Auth%20Flows%20in%20MOO.postman_collection.json\">this collection</a> in postman</p>\n</li>\n<li><p><strong>Important:</strong> Turn off redirect following in postman: File - Settings - Automatically follow redirects</p>\n</li>\n<li><p>Fill in the collection variables (current value):</p>\n<ul>\n<li><p>ClientId</p>\n</li>\n<li><p>ClientSecret</p>\n</li>\n<li><p>RedirectUri</p>\n</li>\n<li><p>StsCookie (requires logging in to the MOO portal)</p>\n</li>\n</ul>\n</li>\n<li><p>Execute the requests in the correct sequence</p>\n</li>\n</ul>\n<img src=\"https://mootooling.blob.core.windows.net/third-party-sso-docs/CollectionVariables.PNG\" alt=\"Edit variables\"></body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"475129","collectionId":"2b987c2e-8a5d-4f4b-bca6-adcfb51aa8f5","publishedId":"S1a7W5f2","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"EF5B25"},"publishDate":"2019-07-01T09:04:25.000Z"},"item":[{"name":"Get code for user code flow","event":[{"listen":"test","script":{"id":"0cdf93f9-3a70-4c44-a684-b4b2efaa7004","exec":["var location = postman.getResponseHeader(\"Location\");\r","var code = location.match(/code=[^&]*/)[0].substr(5);\r","pm.globals.set(\"Code\", code);\r",""],"type":"text/javascript"}}],"id":"fadb6309-4768-4541-82b2-5edf9c08731f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[{"key":"cookie","value":"","type":"text"}],"url":"https://sts.moo.nl/WebSTS/Oauth/Authorize?response_type=code&client_id=&redirect_uri=&scope=user.signin,resapi.user.self.read&state=SomeImpossibleToGuessAntiCsrfValue","description":"<p>Redirect the user's browser to the STS Authorize endpoint to start the SSO auth flow with MOO. If the user is not yet logged in, then she is prompted to login first. If the user is already logged in then the STS automatically redirects the browser back to the calling app, using the redirect uri provided. This redirect uri needs to match the uri configured in MOO to prevent authorization codes leaking to unauthorized parties.</p>\n<h4 id=\"csrf-protection\">CSRF protection</h4>\n<p>To protect your application from CSRF logins it is considered best practice to use the state parameter in the call to the authorization endpoint (see <a href=\"https://spring.io/blog/2011/11/30/cross-site-request-forgery-and-oauth2\">Cross Site Request Forgery and OAuth2</a>). The state parameter should contain some session or time limited data that is not possible to guess by an attacker who attempts to redirect an unwitting user to the authorize endpoint. This state should then be validated by the app before trading the code for an access token and logging the user in. For the test in postman this dummy value is acceptable.</p>\n<h4 id=\"response\">Response</h4>\n<p>Don't worry if the response looks like an error. A user does not normally see this html, because the browser redirects to the redirect_uri immediately.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>status code: 302\nLocation header: https://moo.thirdpartyapp.nl/Account/ExternalLogOnCallback?code=5b6950cdd21e4725946d6bcc8fc777b86c50b3392d764d4ebfd1f151aae5fbb7&amp;state=SomeImpossibleToGuessAntiCsrfValue\n</code></pre><h4 id=\"postman\">Postman</h4>\n<p>For this to work in postman you need to fool the STS into thinking that a logged in user is present. For this you need to set the value of the STS cookie variable to the correct value. The value you need is the key-value pair of the Websts cookie in the domain .moo.nl. It looks like this: \"Websts={base64 string}\". An easy way to get the value of this cookie is to use the browser devtools (i.e. in chrome). Go to the MOO portal and login with the MOO credentials you have been provided with. In dev tools go to the network tab and ensure that the log is preserved between redirects. Then go the the application tab and clear all the cookies in the v2.moo.nl domain and hit refresh.</p>\n<div>\n<img src=\"https://mootooling.blob.core.windows.net/third-party-sso-docs/ClearCookies.PNG\" alt=\"Clear v2.moo.nl cookies\" width=\"700\" />\n</div>\n\n<p>Because the portal user is no longer authenticated the browser is redirected to the sts Authorize endpoint. This call includes the Websts cookie, and it can be retrieved from the request headers.</p>\n<div>\n<img src=\"https://mootooling.blob.core.windows.net/third-party-sso-docs/AuthorizeCall.PNG\" alt=\"Authorize call\" width=\"700\" />\n</div>\n","urlObject":{"protocol":"https","path":["WebSTS","Oauth","Authorize"],"host":["sts","moo","nl"],"query":[{"key":"response_type","value":"code"},{"key":"client_id","value":""},{"key":"redirect_uri","value":""},{"key":"scope","value":"user.signin,resapi.user.self.read"},{"key":"state","value":"SomeImpossibleToGuessAntiCsrfValue"}],"variable":[]}},"response":[],"_postman_id":"fadb6309-4768-4541-82b2-5edf9c08731f"},{"name":"Get user token with code","event":[{"listen":"test","script":{"id":"26c262bd-431d-4ac3-89b5-9fc962753e4b","exec":["var response = JSON.parse(responseBody);","pm.globals.set(\"AccessToken\", response.access_token);","pm.globals.set(\"RefreshToken\", response.refresh_token);"],"type":"text/javascript"}}],"id":"1bdf9165-69d5-434d-a02c-5a605a24ce3c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","name":"Content-Type","value":"application/x-www-form-urlencoded","type":"text"}],"body":{"mode":"urlencoded","urlencoded":[{"key":"grant_type","value":"authorization_code","type":"text"},{"key":"code","value":"{{Code}}","type":"text"},{"key":"redirect_uri","value":"","type":"text"},{"key":"scope","value":"user.signin","type":"text"},{"key":"client_id","value":"","type":"text"},{"key":"client_secret","value":"","type":"text"}]},"url":"https://sts.moo.nl/WebSTS/OAuth/Token","description":"<p>The code that the STS returns to the user's browser should be used by the web application to request an access token. The token call requires the client secret and should therefore be performed by the backend. This flow ensures that the client secret does not have to be passed to the browser. A code can be used only once to request a token, an attempt to reuse a code returns an error.</p>\n<h4 id=\"user-tokens-for-an-multiple-organisations\">User tokens for an multiple organisations</h4>\n<p>MOO supports a single user having different roles in different organisations within a single tenant.</p>\n<p>Access tokens are always organisation specific, and the access they provide is generally limited to the organisation context of any given resource call.</p>\n<p>When the generic OAuth/Token endpoint is used, the organisation of the token is determined by the user's paying (= primary) organisation.</p>\n<p>If the application requires a token for a user in a different member organisation, then a call can be made to the organisation specific token endpoint: OAuth/{OrganisationKey}/Token.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"access_token\": \"eyJ0eXAiO...\",\n    \"token_type\": \"bearer\",\n    \"expires_in\": 1199,\n    \"refresh_token\": \"eyJ0eX...\"\n}\n</code></pre><p>The tokens use the jwt format and can easily be decoded, for example using <a href=\"https://jwt.io/\">jwt.io</a>. The token signature does not have to be verified by the calling application.</p>\n","urlObject":{"protocol":"https","path":["WebSTS","OAuth","Token"],"host":["sts","moo","nl"],"query":[],"variable":[]}},"response":[],"_postman_id":"1bdf9165-69d5-434d-a02c-5a605a24ce3c"},{"name":"Get UserInfo","id":"01a4e9ee-1250-4b8c-9941-fca8709dd490","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{AccessToken}}"}]},"isInherited":false},"method":"GET","header":[],"url":"https://resource.moo.nl/resource/api/v1/userinfo","description":"<p>Use the user token to request user information from the resource api. The actual fields returned depend on the fields that the client has access to, as configured in MOO.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"LoginName\": \"T.Hawk@test01.moo.nl\",\n    \"FirstName\": \"Tony\",\n    \"MiddleName\": null,\n    \"LastName\": \"Hawk\",\n    \"OrganisationKey\": \"65ad2c83-53cc-4e61-842f-04ae904c8417\",\n    \"TenantKey\": \"55380524-4c8b-4483-81e6-f771c2afc340\",\n    \"Key\": \"4e225c26-e984-4987-84ec-6929a98283c3\"\n}\n</code></pre>","urlObject":{"protocol":"https","path":["resource","api","v1","userinfo"],"host":["resource","moo","nl"],"query":[],"variable":[]}},"response":[],"_postman_id":"01a4e9ee-1250-4b8c-9941-fca8709dd490"},{"name":"Get more userinfo","event":[{"listen":"test","script":{"id":"d50b77ed-e00a-4442-8f02-7df14c2e46de","exec":["var response = JSON.parse(responseBody);","pm.globals.set(\"OrganisationKey\", response.PayingOrganisation.Key);"],"type":"text/javascript"}}],"id":"65ff3231-0749-44e1-9924-5957e8185eca","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{AccessToken}}"}]},"isInherited":false},"method":"GET","header":[],"url":"https://resource.moo.nl/resource/api/v1/userinfo/more?select=payingorganisation,firstname,middlename,lastname","description":"<p>Userinfo endpoint with support for more fields via query options. The actual fields returned also depend on the fields the client has access to, as configured in MOO.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"Key\": \"4e225c26-e984-4987-84ec-6929a98283c3\",\n    \"PayingOrganisation\": {\n        \"Key\": \"fd414907-d02d-48f8-a14b-49b0357b45ac\"\n    },\n    \"FirstName\": \"Tony\",\n    \"MiddleName\": null,\n    \"LastName\": \"Hawk\"\n}\n</code></pre>","urlObject":{"protocol":"https","path":["resource","api","v1","userinfo","more"],"host":["resource","moo","nl"],"query":[{"key":"select","value":"payingorganisation,firstname,middlename,lastname"}],"variable":[]}},"response":[],"_postman_id":"65ff3231-0749-44e1-9924-5957e8185eca"},{"name":"Exchange refresh token","event":[{"listen":"test","script":{"id":"8739a1e5-92b5-4d0d-94d2-54625fc8219c","exec":["var response = JSON.parse(responseBody);","pm.globals.set(\"AccessToken\", response.access_token);","pm.globals.set(\"RefreshToken\", response.refresh_token);"],"type":"text/javascript"}}],"id":"fd460f3f-cadf-42eb-b36c-46a1817bdf35","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","name":"Content-Type","value":"application/x-www-form-urlencoded","type":"text"}],"body":{"mode":"urlencoded","urlencoded":[{"key":"grant_type","value":"refresh_token","type":"text"},{"key":"client_id","value":"","type":"text"},{"key":"client_secret","value":"","type":"text"},{"key":"refresh_token","value":"{{RefreshToken}}","type":"text"}]},"url":"https://sts.moo.nl/WebSTS/OAuth/Token","description":"<p>Access tokens have a limited lifetime. Their lifetime length is included in the access_token itself and also directly in the response from the token endpoint. When the access token has expired it is possible to get a new one from the STS without user interaction. This is done using the refresh token, that was included in the original token response. The refresh token has a much longer (or unlimited) lifetime.</p>\n<p>Refresh tokens can be used only once, but the refresh token response also includes a new refresh token with which the refresh process can be initiated again.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"access_token\": \"eyJ0eXAiO...\",\n    \"token_type\": \"bearer\",\n    \"expires_in\": 1199,\n    \"refresh_token\": \"eyJ0eX...\"\n}\n</code></pre>","urlObject":{"protocol":"https","path":["WebSTS","OAuth","Token"],"host":["sts","moo","nl"],"query":[],"variable":[]}},"response":[],"_postman_id":"fd460f3f-cadf-42eb-b36c-46a1817bdf35"},{"name":"Get client token","event":[{"listen":"test","script":{"id":"fa36d43b-94b1-49b1-b06a-93b22cac7571","exec":["var response = JSON.parse(responseBody);","pm.globals.set(\"ClientAccessToken\", response.access_token);"],"type":"text/javascript"}}],"id":"1f2aad9b-90c0-43be-a452-92bbe5d84dd3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","name":"Content-Type","value":"application/x-www-form-urlencoded","type":"text"}],"body":{"mode":"urlencoded","urlencoded":[{"key":"grant_type","value":"client_credentials","type":"text"},{"key":"scope","value":"resapi.user.read","type":"text"},{"key":"client_id","value":"","type":"text"},{"key":"client_secret","value":"","type":"text"}]},"url":"https://sts.moo.nl/WebSTS/OAuth//Token","description":"<p>Get a app specific (in contrast to a user specific) access token using the OAuth client flow. Client tokens are useful for background processing.</p>\n<h4 id=\"organisation-specific-access\">Organisation specific access</h4>\n<p>Third party apps in MOO are restricted to organisation specific access.</p>\n<p>In contrast to the token post in the authorization code flow, the token post in de client flow must include the organisation key of an organisation that has granted access to the third party app.</p>\n<p>If you are executing this request in postman, then the OrganisationKey is extracted from the reponse to the \"Get more userinfo\" call. If you want to test this call without executing the user code flow, then you need to set the OrganisationKey variable by hand to an organisation key that your application has been granted access to.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>{\n    \"access_token\": \"eyJ0eXAiO...\",\n    \"token_type\": \"bearer\",\n    \"expires_in\": 1199\n}\n</code></pre><p>The token uses the jwt format and can easily be decoded, for example using <a href=\"https://jwt.io/\">jwt.io</a>. The token signature does not have to be verified by the calling application.</p>\n","urlObject":{"protocol":"https","path":["WebSTS","OAuth","","Token"],"host":["sts","moo","nl"],"query":[],"variable":[]}},"response":[],"_postman_id":"1f2aad9b-90c0-43be-a452-92bbe5d84dd3"},{"name":"Get organisation users","id":"5a09c468-7d31-46c7-b2d5-4485fe969208","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{ClientAccessToken}}"}]},"isInherited":false},"method":"GET","header":[],"url":"https://resource.moo.nl/resource/api/v1/organisations//users?select=firstname,lastname,usertype","description":"<p>Use the client token to get all the users in an organisation. The actual fields returned depend on the fields the client has access to, as configured in MOO.</p>\n<h4 id=\"response\">Response</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>[\n    {\n        \"Key\": \"580223b4-5a38-4433-9eaa-9a68d117c9cd\",\n        \"FirstName\": \"Admin\",\n        \"LastName\": null,\n        \"UserType\": \"OrganisationAdmin\"\n    },\n    ...\n]\n</code></pre>","urlObject":{"protocol":"https","path":["resource","api","v1","organisations","","users"],"host":["resource","moo","nl"],"query":[{"key":"select","value":"firstname,lastname,usertype"}],"variable":[]}},"response":[],"_postman_id":"5a09c468-7d31-46c7-b2d5-4485fe969208"}],"event":[{"listen":"prerequest","script":{"id":"2d989742-2be2-42de-a7e6-fd1174bdb077","type":"text/javascript","exec":[""]}},{"listen":"test","script":{"id":"a8de29f7-92b9-4cd7-ac24-ebee8fdea6d7","type":"text/javascript","exec":[""]}}],"variable":[{"key":"ClientId","value":""},{"key":"ClientSecret","value":""},{"key":"StsCookie","value":""},{"key":"RedirectUri","value":""},{"key":"OrganisationKey","value":""}]}