Skip to content

Commit 349b027

Browse files
CCM-12075 - Added JWT Bearer token script, instructions and fix for r… (#248)
* CCM-12075 - Added JWT Bearer token script, instructions and fix for ref environment * Update container and git ignore
1 parent 2362ef7 commit 349b027

File tree

6 files changed

+117
-5
lines changed

6 files changed

+117
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ dist
2727
/sandbox/*.log
2828
/sandbox-staging
2929
/specification/api/components/examples
30+
/scripts/JWT/*.pem

scripts/JWT/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Environments – Notify Supplier
2+
3+
## Environment Matrix
4+
5+
| Environment Name | Apigee Instance | Proxy URL | Application | Security Level | Private Key | KID | Notes / Env Code |
6+
|------------------|-----------------|-----------|--------------|----------------|--------------|------|------------------|
7+
| **Internal dev – PRs** | Internal | internal-dev.api.service.nhs.uk/nhs-notify-supplier-PR-XXX | Notify-Supplier-App-Restricted – Internal Dev 2 | level 0 ||| internal-dev PRs |
8+
| **Internal dev – Main** | Internal | internal-dev.api.service.nhs.uk/nhs-notify-supplier | Notify Supplier – Application Restricted – Internal Dev Main | level 3 | [internal-dev-test-1.pem](https://eu-west-2.console.aws.amazon.com/systems-manager/parameters/%252Fnhs%252Fjwt%252Fkeys%252Finternal-dev-test-1.pem/description?region=eu-west-2&tab=Table) | internal-dev-test-1 | dev |
9+
| **Ref** | Internal | ref.api.service.nhs.uk/nhs-notify-supplier | Notify Supplier – Application Restricted – Ref | level 3 | [ref-test-1.pem](https://eu-west-2.console.aws.amazon.com/systems-manager/parameters/%252Fnhs%252Fjwt%252Fkeys%252Fref-test-1.pem/description?region=eu-west-2&tab=Table) | ref-test-1 | prod |
10+
| **Int** | External | int.api.service.nhs.uk/nhs-notify-supplier | Notify Supplier – Integration Testing | level 3 | [int-test-1.pem](https://eu-west-2.console.aws.amazon.com/systems-manager/parameters/%252Fnhs%252Fint%252Fjwt%252Fint-test-1.pem/description?region=eu-west-2&tab=Table) | int-test-1 | int |
11+
12+
---
13+
14+
## How to Get the JWT Access Token
15+
16+
1. Download the private key from AWS Systems Manager and save it locally as `$KID.pem`, for example `ref-test-1.pem`.
17+
18+
2. Get the Apigee API key via one of the following:
19+
- [Apigee Edge Console](https://apigee.com/edge)
20+
- [Internal developer account](https://onboarding.prod.api.platform.nhs.uk/Index)
21+
- [External developer account](https://dos-internal.ptl.api.platform.nhs.uk/Index)
22+
23+
3. Run the Python script `get_bearer_token.py` with the parameters:
24+
25+
```bash
26+
python get_bearer_token.py --kid <KID> --env <ENVIRONMENT> --appid <APIKEY>
27+
```
28+
29+
Example:
30+
31+
```bash
32+
python get_bearer_token.py --kid ref-test-1 --env ref --appid 8Np3gFEw21JX7AGuokId0QEFTaOhG4Z2
33+
```
34+
35+
4. The access token will be returned in the response
36+
37+
5. Add the access token to your API requests as a header:
38+
39+
| Header Name | Header Value |
40+
|--------------|--------------|
41+
| Authorization | `Bearer <access token>` |
42+
43+
---

scripts/JWT/get_bearer_token.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import uuid
2+
import argparse
3+
from time import time
4+
import requests
5+
import jwt # https://github.com/jpadilla/pyjwt
6+
7+
ENV_TOKEN_URLS = {
8+
"int": "https://int.api.service.nhs.uk/oauth2/token",
9+
"internal-dev": "https://internal-dev.api.service.nhs.uk/oauth2-mock/token",
10+
"prod": "https://api.service.nhs.uk/oauth2/token",
11+
"ref": "https://ref.api.service.nhs.uk/oauth2/token",
12+
}
13+
14+
def main():
15+
ap = argparse.ArgumentParser(description="Fetch NHS access token using a signed client assertion (JWT).")
16+
ap.add_argument("--kid", required=True,
17+
help="Base name used for both the private key file (<id>.pem) and the JWT header kid.")
18+
ap.add_argument("--env", choices=ENV_TOKEN_URLS.keys(), required=True,
19+
help="Environment to hit: int, internal-dev, or prod.")
20+
ap.add_argument("--appid", help="Apigee Application ID (used for both sub and iss).")
21+
args = ap.parse_args()
22+
23+
kid = args.kid
24+
private_key_file = f"{kid}.pem"
25+
token_url = ENV_TOKEN_URLS[args.env]
26+
27+
with open(private_key_file, "r") as f:
28+
private_key = f.read()
29+
30+
claims = {
31+
"sub": args.appid,
32+
"iss": args.appid,
33+
"jti": str(uuid.uuid4()),
34+
"aud": token_url,
35+
"exp": int(time()) + 300, # 5mins in the future
36+
}
37+
38+
additional_headers = {"kid": kid}
39+
40+
signed_jwt = jwt.encode(
41+
claims, private_key, algorithm="RS512", headers=additional_headers
42+
)
43+
# ----- 2) Exchange JWT for an access token -----
44+
form = {
45+
"grant_type": "client_credentials",
46+
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
47+
"client_assertion": signed_jwt,
48+
}
49+
50+
resp = requests.post(
51+
token_url,
52+
headers={"content-type": "application/x-www-form-urlencoded"},
53+
data=form,
54+
timeout=30,
55+
)
56+
57+
# Raise for non-2xx responses, then print the token payload
58+
resp.raise_for_status()
59+
token_payload = resp.json()
60+
61+
print("access_token:", token_payload.get("access_token"))
62+
print("expires_in:", token_payload.get("expires_in"))
63+
print("token_type:", token_payload.get("token_type"))
64+
65+
if __name__ == "__main__":
66+
main()

scripts/config/vale/styles/config/vocabularies/words/accept.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
API
1+
API|api
22
[A-Z]+s
33
Bitwarden
44
bot
55
Cognito
66
Cyber
7+
[Dd]ev
78
Dependabot
8-
Dev
99
devcontainer
1010
draw.io
1111
drawio
12+
[Ee]nv
1213
endcapture
1314
endfor
1415
endraw
15-
env
1616
Git[Hh]ub
1717
Gitleaks
1818
Grype

scripts/devcontainer/postcreatecommand.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ source ~/.zshrc
88
make _install-dependencies # required before config to ensure python is available due to race between config:: make targets
99
make config
1010

11+
pip install --user requests pyjwt cryptography
12+
1113
sudo gem install jekyll bundler
1214
jekyll --version && cd docs && bundle install
1315

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type: external
22
healthcheck: /_status
3-
url: https://suppliers.ref.nhsnotify.national.nhs.uk
3+
url: https://main.suppliers.nonprod.nhsnotify.national.nhs.uk
44
security:
55
type: mtls
6-
secret: nhs-notify-supplier-mtls-ref
6+
secret: notify-supplier-mtls-ref

0 commit comments

Comments
 (0)