Skip to content

Commit

Permalink
Update client.py and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
kipparker committed Apr 17, 2024
1 parent 5425b98 commit 55c5caa
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 20 deletions.
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,22 @@ In this simple implementation, the request is stored in a redis instance, using

## Testing the API with client.py

Running client.py will perform the initial steps in the authorisation code flow, outputting a URL that will open the UI to log in and confirm consent. The PKCE code verifier will also be in the output, which will be needed after the redirect
client.py can be used to test authorisation code flow, introspection, id_token decoding and retrieving data from the resource URL.

Four commands are available, and are run using:

```bash
python -W ignore client.py [auth|introspect|id-token|resource]
```

nb. The optional `-W ignore` switch suppresses multiple warnings about the self-signed certificates.

### Auth

Running `client.py auth` will perform the initial steps in the authorisation code flow, outputting a URL that will open the UI to log in and confirm consent. The PKCE code verifier will also be in the output, which will be needed after the redirect

```bash
python -W ignore client.py
python -W ignore client.py auth
```

Example output:
Expand All @@ -68,8 +80,6 @@ Code verifier: c6P-FfD0ayLslzCUESCsay8QHEg71O0SnKLeHPkOSyOZ6KubKPRaclM4u5veKcqI7
https://vigorous-heyrovsky-1trvv0ikx9.projects.oryapis.com/oauth2/auth?client_id=f67916ce-de33-4e2f-a8e3-cbd5f6459c30&response_type=code&redirect_uri=http://127.0.0.1:3000/callback&scope=profile+offline_access&state=9mpb2gDwhp2fLTa_MwJGM21R7FjOQCJq&code_challenge=cksXMlSWrcflDTJoyrpiWX0u2VRV6C--pzetmBIo6LQ&code_challenge_method=S256
```

nb. The `-W ignore` switch suppresses multiple warnings about the self-signed certificates.

By default the client will use the local docker environment and expects a local instance of the FAPI api to be running on localhost:8020. Testing against the deployed API can be achieved by setting the `AUTHENTICATION_API` and `RESOURCE_API` environment variables, and optionally the FAPI_API environment variable.

```bash
Expand All @@ -84,6 +94,26 @@ Granting consent will redirect to our demo client application, with the authoris

![Redirect](docs/exchange.png)

### Introspection

To show the response of the introspection endpoint, run:

```bash
python -W ignore client.py introspect --token <token>
```

with token being the `token` value obtained from authorisation code flow

### Client side id_token decoding

To show the response of client side id_token decoding, run:

```bash
python -W ignore client.py id-token --token <token>
```

with token being the `id_token` value obtained from authorisation code flow

## Ory Hydra

Please contact IB1 for the Client ID and secret if you would like to test against our demo Ory account. Alternatively you can set up a free developer account and create an Oauth2 client with your own details. The client should have:
Expand Down
1 change: 1 addition & 0 deletions authentication/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ types-requests = "*"
pytest = "*"
responses = "*"
pytest-mock = "*"
click = "*"

[requires]
python_version = "3.12"
2 changes: 1 addition & 1 deletion authentication/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions authentication/api/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}


FAPI_API = os.environ.get("FAPI_API", "https://perseus-demo-fapi.ib1.org")
ISSUER_URL = os.environ.get("ISSUER_URL", "https://perseus-demo-energy.ib1.org")
CLIENT_ID = os.environ.get("CLIENT_ID", "21653835348762")
CLIENT_SECRET = os.environ.get(
"CLIENT_SECRET", "uE4NgqeIpuSV_XejQ7Ds3jsgA1yXhjR1MXJ1LbPuyls"
Expand All @@ -32,6 +32,6 @@
"https://musing-kirch-t48np94ikp.projects.oryapis.com/admin/oauth2/introspect",
)
REDIRECT_URI = os.environ.get(
"REDIRECT_URI", "https://perseus-demo-authentication.ib1.org/callback"
"REDIRECT_URI", "https://perseus-demo-accounting.ib1.org/callback"
)
REDIS_HOST = os.environ.get("REDIS_HOST", "redis")
49 changes: 37 additions & 12 deletions client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import pkce
import time
import secrets

import click

from authentication.api import conf
import ssl

Expand Down Expand Up @@ -124,17 +127,17 @@ def client_side_decoding(token: str):
# Workaround for self-signed certificates, insecure
ssl._create_default_https_context = ssl._create_unverified_context

jwks_url = conf.FAPI_API + "/.well-known/jwks.json"
jwks_url = conf.ISSUER_URL + "/.well-known/jwks.json"
print(jwks_url)
jwks_client = jwt.PyJWKClient(jwks_url)
header = jwt.get_unverified_header(token)
key = jwks_client.get_signing_key(header["kid"]).key
decoded = jwt.decode(token, key, [header["alg"]], audience=f"{conf.CLIENT_ID}")
print(decoded, conf.FAPI_API)
print(decoded, conf.ISSUER_URL)
# Example of tests to apply
if decoded["aud"] != conf.CLIENT_ID:
raise ValueError("Invalid audience")
if decoded["iss"] != conf.FAPI_API:
if decoded["iss"] != conf.ISSUER_URL:
raise ValueError("Invalid issuer")
if decoded["exp"] < int(time.time()):
raise ValueError("Token expired")
Expand All @@ -144,11 +147,25 @@ def client_side_decoding(token: str):
return decoded


if __name__ == "__main__":
# Initiate flow with PAR
@click.group()
def cli():
pass

# Generate PKCE code verifier and challenge

@click.option("--token", help="introspect token returned from authorisation flow")
@cli.command()
def introspect(token):
print(introspect_token(token))


@click.option("--token", help="Decode ID token returned from authorisation flow")
@cli.command()
def id_token(token):
print(client_side_decoding(token))


@cli.command()
def auth():
code_verifier, par_response = pushed_authorization_request()
print("Code verifier: ", code_verifier)
session = get_session()
Expand All @@ -165,13 +182,21 @@ def client_side_decoding(token: str):
if response.status_code == 302:
print(response.headers["location"])
else:
print(response.status_code, response.text)
print("Error:", response.status_code, response.text)


if __name__ == "__main__":
cli()
# Initiate flow with PAR

# Generate PKCE code verifier and challenge

# The following two tests will use the values returned after login and consent has been given
print(
introspect_token(
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJjbGllbnRfaWQiOiJmNjc5MTZjZS1kZTMzLTRlMmYtYThlMy1jYmQ1ZjY0NTljMzAiLCJleHAiOjE3MTMyODU5MjUsImV4dCI6e30sImlhdCI6MTcxMzI4MjMyNSwiaXNzIjoiaHR0cHM6Ly92aWdvcm91cy1oZXlyb3Zza3ktMXRydnYwaWt4OS5wcm9qZWN0cy5vcnlhcGlzLmNvbSIsImp0aSI6ImNjYTQ5N2Y1LWYzYjAtNGM4MS1iODczLTdmOTdhNzRjZmNkYSIsIm5iZiI6MTcxMzI4MjMyNSwic2NwIjpbInByb2ZpbGUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiJkNmZkNmUxYy1hMTBlLTQwZDgtYWEyYi05NjA2ZjNkMzRkM2MiLCJjbmYiOnsieDV0I1MyNTYiOiJrNkpvY19UYlJJbV92SVF5cldjTVRJVnpfUVptUjBKUmVHQVNXUmNMZG5RIn19.SxM9YvqE-vvXwemHNbLHNey7xbyLGsGu4T6bSmmhXNP2-nk8GMcmoHCLXhgYhQFJ3HcuLx7P9kQCqEUrY68xGQ"
)
)
# print(
# introspect_token(
# "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJjbGllbnRfaWQiOiJmNjc5MTZjZS1kZTMzLTRlMmYtYThlMy1jYmQ1ZjY0NTljMzAiLCJleHAiOjE3MTMyODU5MjUsImV4dCI6e30sImlhdCI6MTcxMzI4MjMyNSwiaXNzIjoiaHR0cHM6Ly92aWdvcm91cy1oZXlyb3Zza3ktMXRydnYwaWt4OS5wcm9qZWN0cy5vcnlhcGlzLmNvbSIsImp0aSI6ImNjYTQ5N2Y1LWYzYjAtNGM4MS1iODczLTdmOTdhNzRjZmNkYSIsIm5iZiI6MTcxMzI4MjMyNSwic2NwIjpbInByb2ZpbGUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiJkNmZkNmUxYy1hMTBlLTQwZDgtYWEyYi05NjA2ZjNkMzRkM2MiLCJjbmYiOnsieDV0I1MyNTYiOiJrNkpvY19UYlJJbV92SVF5cldjTVRJVnpfUVptUjBKUmVHQVNXUmNMZG5RIn19.SxM9YvqE-vvXwemHNbLHNey7xbyLGsGu4T6bSmmhXNP2-nk8GMcmoHCLXhgYhQFJ3HcuLx7P9kQCqEUrY68xGQ"
# )
# )
# print(
# client_side_decoding(
# "eyJhbGciOiJFUzI1NiIsImtpZCI6IjEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3BlcnNldXMtZGVtby1lbmVyZ3kuaWIxLm9yZyIsInN1YiI6ImQ2ZmQ2ZTFjLWExMGUtNDBkOC1hYTJiLTk2MDZmM2QzNGQzYyIsImF1ZCI6ImY2NzkxNmNlLWRlMzMtNGUyZi1hOGUzLWNiZDVmNjQ1OWMzMCIsImV4cCI6MTcxMzI3OTgxMiwiaWF0IjoxNzEzMjc2MjEyLCJraWQiOjF9.SHpel4gQyrIS6RNM4VTZgsepgR-g-g5zQWeLwBVUzapeusDU2tsfT4yCczN6XMNYq9xCuL2WmIVEWKJBonp2Gw"
Expand Down
1 change: 0 additions & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ services:
stop_signal: SIGINT
environment:
- ISSUER_URL=http://host.docker.internal:8020
# - FAPI_API=https://perseus-demo-fapi.ib1.org
command:
[
"uvicorn",
Expand Down

0 comments on commit 55c5caa

Please sign in to comment.