-
Register your 'resource' (your API) in AD FS
Your ‘resource’, or API, is registered in AD FS by configuring a ‘Relying Party Trust’ (RPT).
AD FS will issue access tokens in the form of JSON Web Tokens (JWTs). JWTs contain ‘claims’ (attributes) that contain information that can be used to validate the token, and to determine e.g. the user’s id and role. It also has a digital signature that prevents tampering with the claims.
AD FS allows for customizing the JWT claims. This is also done in the RPT configuration:
- Go to the appropriate Windows Server.
- Open AD FS Management from the Tools menu in Server Manager.
- Select “Add Relying Party Trust…” on the Actions pane.
- Select the “Enter manually” option.
- Provide a Display name that describes your resource.
- Select “AD FS profile” (not “AD FS 1.0 and 1.1 profile”).
- Skip through the Certificate screen (leave all of the certificate related features as is).
- Skip through the URL screen (leave all options unselected).
- Provide an identifier for this RPT.
(This can be any string, as long as it is unique, e.g. “new-enterprise-api”. It can be a url or uri, but it doesn’t have to be; it is not used to ‘call’ anything outside of AD FS, it is just used as an internal id.)
- Choose whether you want AD FS to ask for multi-factor authentication for this RPT.
Select “No” to keep it simple.
- Choose whether you want to permit or deny access to this resource by default (you can later specify authorization rules to deviate from the default).
Select “Permit” to keep it simple.
- Go to the end of the Wizard, make sure that “Open Edit Claim Rules dialog” is checked and close the Wizard.
- The Edit Claim Rules dialog will open; click “Add Rule…”
- Select the “Send LDAP Attributes as Claims” option and click Next.
- Name the Rule, e.g. “User info“.
- As a minimum you’ll probably want to add the user’s email address as a claim, so you can identify the user. You could also add e.g. the user’s display name.
- If you want to do something with user roles, you could add another claim rule, e.g. “Send Group Membership as a Claim“; there you can select an AD FS group that a user may be a member of and define an outgoing claim “Role” and give it a value corresponding to that group. That way you can assign roles to users depending on what groups they are a member of. For multiple roles, create a new claim rule for each group/role. If a user has more than one role, the ‘Role’ claim will contain an array of roles.
- Click Finish and then OK until all dialogs are gone; your API is now configured in AD FS.
This is well described (with screenshots etc) in Vittorio Bertocci’s blog on cloudidentity.com.
Official Microsoft documentation: Create a Relying Party Trust.
-
Register your 'client' (your app) in AD FS
Another thing you need to register in AD FS is your ‘client’ (the front-end application, web app, mobile app, etc) that uses the ‘resource’ (the API).
In AD FS 3.0, clients are not managed throuhg the UI. You have to configure them via PowerShell. You can use the Server Manager to access the ADFS PowerShell module and open a prompt.
To add an AD FS client, you use the cmdlet “Add-ADFSClient“. As a minimum, you have to provide:
- a unique identifier (just like the RPT identifier, unique to AD FS internally),
- a descriptive name,
- the redirect uri (this is what AD FS redirects the user to after successful authentication; the ‘callback endpoint’ in the application that takes the authorization code that is appended to the redirect uri in the “code” parameter).
Additionally, you could add a description.
Example:
(line breaks only for readability purposes)
PS C:\> Add-AdfsClient -ClientId "new-enterprise-app"
-Name "New Enterprise Application"
-RedirectUri "https://new-enterprise-app.eu-gb.mybluemix.net/auth"
-Description "OAuth 2.0 client for our new enterprise application"
To check the AD FS client configuration you just made, use the Get-AdfsClient cmdlet, with the client id:
PS C:\> Get-AdfsClient -ClientId "new-enterprise-app"
To change anything to an already created client, use the Set-AdfsClient cmdlet, but this time, use the TargetClientId parameter (instead of ClientId):
PS C:\> Set-AdfsClient -TargetClientId "new-enterprise-app"
-RedirectUri "https://new-enterprise-app.eu-gb.mybluemix.net/auth2"
-
Obtain the token signing certificate from AD FS
In order to validate the signature of the access tokens issued by AD FS, you will need the public key, which is contained in the token signing certificate:
- Open AD FS
- Go to Service > Certificates.
- Click the Token-signing certificate.
- In the Actions section, click View Certificate.
- On the Details tab, click Copy to File, and click Next.
- Select Base-64 encoded X.509 (.CER), and click Next.
- Click Browse, select a location, enter a file name, and click Save.
- Click Next, then click Finish.
-
Create the callback endpoint in your client
Since we setup the RedirectUri as “https://new-enterprise-app.eu-gb.mybluemix.net/auth2”, after successful user authentication, AD FS will redirect the user to this url and will do an http GET request to the app’s callback endpoint with the authorization code it in the “code” parameter, like this:
GET /auth2?code=<autorization code>
HTTP/1.1
Host: <https://new-enterprise-app.eu-gb.mybluemix.net>
So your client, the front-end app, must have the callback endpoint /auth2 that can take the authorization code from the “code” parameter.
How this is implemented depends on the type of app you have (mobile app, web app (running on the server, or in the browser), etc).
For a mobile app, the redirect uri may look different than for a web app.
-
Obtain the access token in your client
Here I’d like to give credit to Chris Price, who’s crystal clear blog on scottlogic.com helped me a lot with the steps described below.
1. AUTHORIZATION REQUEST
If the front-end app does not yet have a user session, it must redirect the user to the AD FS authorization endpoint.
GET /adfs/oauth2/authorize?response_type=code
&client_id=<application identifier>
&resource=<relying party trust identifier>
&redirect_uri=<application callback url>
HTTP/1.1
Host: <adfs sever hostname>
2. USER LOGIN
Sign-in is handed off to the standard ADFS login screen (if the user doesn’t have a session yet with AD FS)
or you’re implicitly signed in (if the user already has a session with AD FS).
3. AUTHORIZATION GRANT
After successful login, ADFS will respond with:
HTTP/1.1 302 Found
Location: <application callback url>?code=<autorization code>
Which has to be handled by the client’s callback endpoint mentioned in the previous step of this recipe.
4. ACCESS TOKEN REQUEST
With the authorization code obtained in the previous step, the client can now call the AD FS token endpoint to request an access token with the following POST request:
POST /adfs/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: <adfs sever hostname>
Content-Length: <length of content>
grant_type=authorization_code
&client_id=<application identifier>
&redirect_uri=<application callback url>
&code=<autorization code>
–> However, this will most likely not work if the client is a single page (e.g. Angular.js) app running on a different (sub)domain than AD FS and it tries to execute this POST request from within the browser as an XHR request, because CORS is not enabled by default on AD FS and the browser will block it due to a violation of the the same-origin policy.
If this is the case, you have two options:
- Enable CORS on AD FS.
(but if AD FS is not installed with IIS as the web server, this may prove to be difficult, if not impossible)
- Create a token endpoint proxy service that has CORS enabled, and which will forward your request for a token server side (i.e. not from a browser) which will circumvent the same-origin policy.
Since in our case AD FS was installed without IIS, we chose option 2.
5. ACCESS TOKEN
The access token is returned in the following http response:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token":<access_token>,
"token_type":"bearer",
"expires_in":3600
}
-
Pass the access token with every API call
Now de client has the access token, it can call the APIs on API Connect, passing the token in the Authorization http header as a Bearer token.
Apart from the token, we also require the client to provide an api key in the http header X-IBM-Client-Id.
So we provide two http headers for API calls to API Connect:
X-IBM-Client-Id, value: <api key>
Authorization, value: Bearer <access token>
-
Validate the access token in API Connect
The access token provided by AD FS is signed using the RSA SHA256 algorithm, i.e. it is signed with a private key and you need the corresponding public key in order to validate the signature.
In step 3, I described how to get the token signing certificate from AD FS, which contains the public key for verifying the token’s signature.
Ideally, you would store the token signing certificate in API Connect and refer to it as a “Verify Crypto Object”. But unfortunately, in IBM Cloud you cannot store a certificate in API Connect. You might be able to have it stored by someone from IBM Cloud support by creating a support ticket for it, be we decided to extract the public key from the certificate and convert it into a JSON Web Key (JWK).
This is how you do that:
1. Obtain the PEM (Base64) formatted public key from the PEM (Base64) encoded certificate:
openssl x509 -pubkey -noout -in ADFS_Token_Signing_Cert_Base64.cer > pubkey_base64.key
2. Convert the PEM encoded public key to JWK format using the node.js pem-jwk tool:
npm install -g pem-jwk
pem-jwk pubkey_base64.key > pubkey.jwk
3. Put the contents of pubkey.jwk into a variable in APIC and reference this variable in the “Validate JWT” routine under “Verify Crypto JWK variable name”
The JWK will contain the:
- key type “kty”
- modulus “n”
- exponent “e”
Example:
(line breaks are for readability only)
{
"kty":"RSA",
"n" :"cFrt3JkorT10otreQd",
"e" :"AQAB"
}
In the screenshot below you see that we set a variable “pubkeyJWK” to the value of the JWK above:
And we reference this variable in the ‘Validate JWT’ policy under “Verify Crypto JWK variable name”:
Furthermore, we specify the appropriate Issuer and Audience Claims.
Now API Connect will validate the access token provided with each API call by checking:
- the issued timestamp (must be in the past)
- the expires timestamp (must be in the future)
- the issuer
- the audience
- the signature (so the claims cannot be tampered with)
-
Lock down your backend services (if they're running on public cloud)
Finally, after we established all of the above to secure the APIs on API Connect (APIC) via OAuth 2.0 using AD FS, we have to prevent that our backend services’ APIs can be accessed directly, bypassing API Connect!
We did this by using the native API Management layer around each Cloud Foundry runtime.
By switching this on, and requiring an API key & secret to access those APIs, we prevented free access to those services.
API Connect in turn, is then the only device that gets the API keys & secrets for our backend services.
How to turn on API Management for a Cloud Foundry app:
- Open the Cloud Foundry App dashboard on Bluemix
- Go to API Management
- Click “Get Started”
- Flick the switch that says: “Require applications to authenticate via API key”
- Select Method: “API key and secret”
- Scroll all the way down and click the “Save” button
- On the top right, flick the switch that says “Expose Managed API”
- Go to the “Sharing & keys” tab
- Under “Sharing within Cloud Foundry organization”, click the “Create API key” button
- Give the key a name that describes who/what is using it (like “APIC”)
- Click the “Show” checkbox
- Copy the API secret to your clipboard (it will not be visible ever again!!)
- Paste the API secret into an APIC Variable message.headers.X-IBM-Client-Secret
- Copy the API key to your clipboard
- Paste the API key into an APIC Variable message.headers.X-IBM-Client-Id
- Click the “Create” button the create the API key
- Save the APIC configuration with the new API key & secret
- Click “← All APIs” to go back
-
Improvements
The previous steps cover the basics for obtaining an access token from AD FS, passing this token to API Connect and have API Connect validate the token.
There is still room for improvement, or additional functionality:
- Refresh tokens: Usability for the end user and security can be improved if refresh tokens are enabled on AD FS.
- Usability, because the user wouldn’t have to re-login when the access token expired (by default after 1 hour).
- Security, because the use of refresh tokens allows for a shorter time-to-live for the access tokens (since those can then be refreshed seamlessly in the background). Since access tokens are ‘Bearer’ tokens, they can be (ab)used by anyone who happens to have it. So a shorter time-to-live reduces that risk and reduces a possible attack window.
- Role based authorization: If your users have different roles and responsibilities, you can configure AD FS to add user roles to the access token.
- The client app can use those roles to display and hide stuff in de UI.
- API Connect can use those roles to allow or deny access to individual endpoints.
I may write additional recipes on those topics in the future, so stay tuned!