The goal of the next two posts is to provide you with methods for authentication of your APIs. I will not be going thru all the methods that a ELM server support authentication, but at a high level your server can either be setup to use application level authentication or to delegate that authentication to some external service. If you utilize application level authentication your APIs should be using OAuth 1.0a. If you utilize delegated authentication, you will be using a Bearer token with your APIs.
Let’s begin with OAuth 1.0a:
As I pointed out in the blog post on “Beginning API Discovery”, each server exposes a set of key end points which are required as part of OAuth. Before using those APIs we will need to access our server as an admin and create a key / secret pair, with the appropriate access we will need for our APIs.
- We will log in as elmadmin (utilize an appropriate user ID with server administrative authority on your server), on our Requirements Management Server, and access the Server Admin page.
- Select Consumers to access the OAuth Consumers page. At the top of the page we will register a new “Oauth Consumer” and the system will generate a key for us. We need to provide the following information: consumer name (this is informational only), a consumer secret (which will be used as part of our OAuth1.0a process), and select trusted.
- The system will provide you with the Consumer Key, but you still need to assign a functional user ID. To assign the ID go to the bottom of the page and select the pencil (edit) under actions, and then choose “Select User”. Search for and select the appropriate user id (elmadmin on my server), choose OK, and select Finish.
- Your server is now set and you have all the information to actually authenticate your API calls to the ELM server utilizing OAuth 1.0a. If you don’t have authority to do the above steps, you will need to work with your server administrator who will provide you with the Consumer Key and Secret, you will need to use in authentication before you API can use OAuth.
What is OAuth?
OAuth is a standard for allowing access to protected resources without disclosing user credentials. OAuth is defined at https://oauth.net/core/1.0a/ and consists of three steps (as defined in section 6 of the previous link). For this purposes of this post, I will assume that you have read thru the details of OAuth and are looking for a practical approach of how to implement it with your API flow.
Remember our RootServices
In a prior blog post (Beginning API Discovery) I pointed out the following information being provided in the RootServices document:
<jfs:oauthDomain>https://elmwb.com:9443/jts,https://elmwb.com:9443/rm</jfs:oauthDomain>
<jfs:oauthRealmName>Jazz</jfs:oauthRealmName>
<jfs:oauthAccessTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-access-token" />
<jfs:oauthApprovalModuleUrl rdf:resource="https://elmwb.com:9443/jts/_ajax-modules/com.ibm.team.repository.AuthorizeOAuth" />
<jfs:oauthExpireTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-expire-token" />
<jfs:oauthRequestConsumerKeyUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-consumer" />
<jfs:oauthRequestTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-token" />
<jfs:oauthUserAuthorizationUrl rdf:resource="https://elmwb.com:9443/jts/oauth-authorize" />
For our API process we need three key end points – <jfs:oauthAccessTokenUrl>, <jfs:oauthRequestTokenUrl>, and <jfs:oauthUserAuthorizationUrl>. These API end points support our process as defined above in the OAuth standard link.
There are many libraries and other tools which may make it easier for you to deal with OAuth; however, I’d like to take you thru the entire flow in this post. This will help you understand how the APIs work, that you just discovered in the RootServices API.
High-level OAuth Flow
It looks pretty easy doesn’t it? Seriously, it will take a bit to break it down. Using our specific APIs from the RootServices document, I’ll also show you the headers, and how the redirects work.
Step 1 – We begin with the initial Post in the top left of the above image. This maps to section 6.1 in the Oauth specification for obtaining an unauthorized Request Token. This token can only be used once, and requires providing your oauth consumer key, oauth consumer secret, a signature method, oauth signature, and a oauth nonce. We support HMAC-SHA1 for the signature method. Creating your oauth signature is defined in section 9.1 of the above Oauth standard. The nonce, is any unique value you wish to provide, as nonce means – “number used once”, a full time stamp usually works fine for this.
curl --location --request POST ‘https://elmwb.com:9443/jts/oauth-request-token?oauth_consumer_key=92cd70d34ec442329b5651d3d50bfa76&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1645136440&oauth_nonce=HCkegHwV6nx&oauth_signature=RU29jNzYtT%2FLKBOQEjVmfa2FVeg%3D'
As you can see from the above CURL command, I am passing a consumer key of 92cd70d34ec442329b5651d3d50bfa76. My secret was used in the encoding of the signature.
The response is very simple:
oauth_token=b87ebfa4fc934690b561720ae6e3af10&oauth_token_secret=B3tvnNvd1xeT1mZ2Fcz0EAQkDI1kmp487lCfGOlrDLM
As you can see I am returned an unauthorized Token and Secret, which I will use in the next step.
Step 2 – Now that we have our unauthorized token, we need to exchange it for an authorized token. This is our <jfs:oauthUserAuthorizationUrl> API, again from our RootServices document. We will pass the oauth_token we received in the prior step, to get a authorized token and secret.
curl --location --request GET 'https://elmwb.com:9443/jts/oauth-authorize?oauth_token=9bef1a97db994466a79930d769cb0f7f'
This will begin a series of redirects (Status code 302) that we will want to follow-until we execute an authorization request from the client application. This allows for the client to provide the authentication, indicating your credentials are authorized. Let’s go thru the redirects to understand what is going on.
Key response headers:
X-com-ibm-team-repository-web-auth-msg: authrequired
Location: https://elmwb.com:9443/jts/secure/authenticated/identity?redirectPath=%2Fjts%2Foauth-authorize%3Foauth_token%3Db25d99ad678f47e99daa4d20be21ab63
To process this redirect, I will perform a GET against the value provided by the location header:
curl --location --request GET 'https://elmwb.com:9443/jts/auth/authrequired' \
--header 'Accept: application/rdf+xml’
This will provide a second redirect (Status code 302), along with the following key response header:
Location: https://elmwb.com:9443/jts/auth/authrequired
Once again, we take the location and execute another GET looking for a Status code of 200, and the “X-com-ibm-team-repository-web-auth-msg with the message ‘authrequired’”.
curl --location --request GET 'https://elmwb.com:9443/jts/auth/authrequired'
We can now perform the appropriate user authentication required by our server. On my ELM server the API end point is j_security_check, this will not be the case if you are using a different server to authenticate your user id.
curl --location --request POST 'https://elmwb.com:9443/rm/j_security_check' \
--header 'oauth_token: 9bef1a97db994466a79930d769cb0f7f' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'j_username=elmadmin' \
--data-urlencode 'j_password=elmadmin'
As you can see the above API, I am passing an encoded form with the user id and password, along with the header, which includes my authorized oauth_token. Our response should be another redirect (302), with a location header containing the redirect we will follow:
https://elmwb.com:9443/jts/secure/authenticated/identity?redirectPath=%2Fjts%2Foauth-authorize%3Foauth_token%3Dd73245874ae24736ac54b4d99cbe8536
This redirect will provide us with the URL of the Oauth-Authorize function, with the original unauthorized Oauth Request token from Step 1. We will follow the redirect and related response location header, until we receive a Status code of 200. On my server this takes two calls. The final call provides a response explaining what authentication I have:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>OAuth Consumer Authorization</title>
<link rel="stylesheet" type="text/css" href="web/com.ibm.team.repository.web/ui/oauth/templates/Authorize.css" />
<script language="JavaScript" src="web/com.ibm.team.repository.web/ui/oauth/Authorize.js"></script>
</head>
<body>
<div class="boxDiv">
<div class="content">
<div clas="header">
<h4>You have allowed the server "elmadmin" to collaborate with the server "https://elmwb.com:9443/jts/" using the "elmadmin" user ID. </h4>
</div>
</div>
</div>
</body>
</html>
Step 3 – At this point, you call the third API identified in the RootServices document – <jfs:oauthAccessTokenUrl>. Which will provide you with an authenticated token and secret.
curl --location --request GET 'https://elmwb.com:9443/jts/oauth-access-token' \
--header 'Authorization: OAuth oauth_consumer_key=“b20fa15ba1d94a26a62f484b3bf701f9",oauth_token="d73245874ae24736ac54b4d99cbe8536",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1645540874",oauth_nonce="pi6DEJIQwVm",oauth_signature="%2Fyaxha74xspsecKRNWKsOigShSA%3D"
As you can see I am passing my consumer Key and my original token (which has been authenticated). The response back is simply:
oauth_token=02334c23bc734c48a519ed75a5359f94&oauth_token_secret=moY6vTLGKkUd3PPlmMTb3X0ApgirO9enXjvOSgEVoI
We are now ready to use our Oauth1.0a credentials. If we want a list of Project Areas on this sever we would simply provide the following:
curl --location --request GET 'https://elmwb.com:9443/rm/process/project-areas?oauth_consumer_key=b20fa15ba1d94a26a62f484b3bf701f9&oauth_token=02334c23bc734c48a519ed75a5359f94&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1645541598&oauth_nonce=oKMVvxXC0LC&oauth_signature=5HUQG2MJzA%2Bk%2FsbfuLdiGhP2mkQ%3D' \
--header 'OSLC-Core-Version: 2.0' \
--header 'Accept: application/json’
Mapping back to our our above steps we see:
- We are using our original Consumer Key from the ELM server
- We are using our returned Oauth Token from the response of our third API call above.
- The oauth signature method, timestamp, nonce and signature are all created as defined above.
If you are familiar with Postman, here are two files to help you process these same steps:
Postman Environment File – OAuth1.0a Demo Environment
Postman API Collection – OAuth1.0a Flow
Wow! That was a lot of work. This post took a lot longer than I expected. There has to be an easier way, and there is! I will cover that in the next post, on how to use OIDC (OpenID Connect – https://openid.net/connect/ ).