Skip to content

Commit 034bb1a

Browse files
committed
Statnett-255: Frontend app as a separate app, which is registered
1 parent 89ab79f commit 034bb1a

File tree

6 files changed

+49
-19
lines changed

6 files changed

+49
-19
lines changed

docs/FastAPI.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ The `version` information is included in the response from the `/__about` endpoi
6969

7070
* `SECURITY_ENABLED` - OPTIONAL, DEFAULT=False, Exposed to the UI - Indicates if security is enabled.
7171
* `SECURITY_CLIENT_ID` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The registered application (client) ID.
72+
* `SECURITY_FRONTEND_APP_CLIENT_ID` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The registered frontend application (client) ID.
7273
* `SECURITY_AUTHORITY` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The authority URL used for authentication.
7374
* `SECURITY_LOGOUT` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The logout endpoint URL.
7475
* `SECURITY_LOGIN_REDIRECT` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The URL to redirect to after a successful login.
7576
* `SECURITY_LOGOUT_REDIRECT` - REQUIRED iff `SECURITY_ENABLED=True`, Exposed to the UI - The URL to redirect to after logout.
7677
* `SECURITY_OIDC_DISCOVERY_URL` - OPTIONAL, DEFAULT=`{SECURITY_AUTHORITY}/v2.0/.well-known/openid-configuration` - OpenID Connect Discovery URL.
7778
* `SECURITY_AUDIENCE` - REQUIRED iff `SECURITY_ENABLED=True` - The expected audience of the security tokens.
79+
* `SECURITY_ISSUER` - REQUIRED iff `SECURITY_ENABLED=True` - The expected issuer of the security tokens.
7880
* `SECURITY_TTL` - OPTIONAL, DEFAULT=`86400` seconds (24 hours), must be >= 1 - Indicates how many seconds to cache the public keys and the issuer obtained from the OpenID Configuration endpoint.
7981
According to [the Azure documentation](https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens) a reasonable frequency to check for updates to the public keys used by Microsoft Entra ID is every 24 hours.
8082

src/talk2powersystemllm/app/models/auth.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
class AuthConfig(BaseModel):
55
enabled: bool
66
client_id: str | None = Field(alias="clientId")
7+
frontend_app_client_id: str | None = Field(alias="frontendAppClientId")
8+
scopes: list[str] | None
79
authority: str | None
810
logout: str | None
911
login_redirect: str | None = Field(alias="loginRedirect")

src/talk2powersystemllm/app/server/auth.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
security_scheme = HTTPBearer(auto_error=False)
1616

1717

18-
def get_jwks_uri_and_issuer() -> tuple[str, str]:
18+
def get_jwks_uri() -> str:
1919
try:
2020
oid_config = requests.get(settings.security.oidc_discovery_url)
2121
oid_config.raise_for_status()
@@ -29,15 +29,13 @@ def get_jwks_uri_and_issuer() -> tuple[str, str]:
2929
json_response_body = oid_config.json()
3030
if "jwks_uri" not in json_response_body:
3131
raise HTTPException(status_code=500, detail="jwks_uri not found in the OpenID Configuration")
32-
if "issuer" not in json_response_body:
33-
raise HTTPException(status_code=500, detail="issuer not found in the OpenID Configuration")
3432

35-
return json_response_body["jwks_uri"], json_response_body["issuer"]
33+
return json_response_body["jwks_uri"]
3634

3735

3836
@cached(cache=TTLCache(maxsize=1, ttl=settings.security.ttl))
39-
def get_jwks_keys_and_issuer() -> tuple[dict, str]:
40-
jwks_uri, issuer = get_jwks_uri_and_issuer()
37+
def get_jwks_keys() -> dict:
38+
jwks_uri = get_jwks_uri()
4139
try:
4240
keys = requests.get(jwks_uri)
4341
keys.raise_for_status()
@@ -47,17 +45,17 @@ def get_jwks_keys_and_issuer() -> tuple[dict, str]:
4745
exc_info=err
4846
)
4947
raise HTTPException(status_code=500, detail="Fail to get issuer keys")
50-
return keys.json(), issuer
48+
return keys.json()
5149

5250

5351
def verify_jwt(token: str):
54-
jwks_keys, issuer = get_jwks_keys_and_issuer()
52+
jwks_keys = get_jwks_keys()
5553
try:
5654
return jwt.decode(
5755
token,
5856
jwks_keys,
5957
audience=settings.security.audience,
60-
issuer=issuer,
58+
issuer=settings.security.issuer,
6159
)
6260
except ExpiredSignatureError as e:
6361
logging.warning("Expired Signature", exc_info=e)

src/talk2powersystemllm/app/server/config.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class SecuritySettings(BaseSettings):
1717
default=None,
1818
description="The registered application (client) ID. The value is also exposed to the UI."
1919
)
20+
frontend_app_client_id: str | None = Field(
21+
default=None,
22+
description="The registered frontend application (client) ID. The value is also exposed to the UI."
23+
)
2024
oidc_discovery_url: str | None = Field(
2125
default=None,
2226
description="OpenID Connect Discovery URL."
@@ -25,6 +29,10 @@ class SecuritySettings(BaseSettings):
2529
default=None,
2630
description="The expected audience of the security tokens"
2731
)
32+
issuer: str | None = Field(
33+
default=None,
34+
description="The expected issuer of the security tokens"
35+
)
2836
ttl: int = Field(
2937
default=86400,
3038
ge=1,
@@ -51,11 +59,12 @@ class SecuritySettings(BaseSettings):
5159

5260
@model_validator(mode="after")
5361
def check_required_fields_and_set_default_oidc_discovery_url(self) -> "SecuritySettings":
54-
if self.enabled and ((not self.client_id) or (not self.authority)
55-
or (not self.logout) or (not self.login_redirect) or (not self.logout_redirect)
56-
or (not self.audience)):
62+
if self.enabled and ((not self.client_id) or (not self.frontend_app_client_id) or (not self.authority)
63+
or (not self.issuer) or (not self.logout) or (not self.login_redirect)
64+
or (not self.logout_redirect) or (not self.audience)):
5765
raise ValueError("If security is enabled, the following fields are required: "
58-
"client_id, authority, logout, login_redirect, logout_redirect, audience!")
66+
"client_id, frontend_app_client_id, authority, logout, "
67+
"login_redirect, logout_redirect, audience!")
5968
if self.enabled and not self.oidc_discovery_url:
6069
self.oidc_discovery_url = (
6170
self.authority.rstrip("/") + "/v2.0/.well-known/openid-configuration"

src/talk2powersystemllm/app/server/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ async def lifespan(_: FastAPI):
127127
authority=settings.security.authority,
128128
client_credential=agent_factory.settings.tools.cognite.client_secret,
129129
)
130-
COGNITE_SCOPES = [f"{agent_factory.settings.tools.cognite.base_url}/.default", "offline_access"]
130+
COGNITE_SCOPES = [f"{agent_factory.settings.tools.cognite.base_url}/.default"]
131131

132132
GraphDBHealthchecker(agent_factory.graphdb_client)
133133

@@ -410,6 +410,8 @@ async def get_auth_config(
410410
return AuthConfig(
411411
enabled=settings.security.enabled,
412412
clientId=settings.security.client_id,
413+
frontendAppClientId=settings.security.frontend_app_client_id,
414+
scopes=["openid", "profile", f"{settings.security.audience}/.default"],
413415
authority=settings.security.authority,
414416
logout=settings.security.logout,
415417
loginRedirect=settings.security.login_redirect,
@@ -474,7 +476,7 @@ async def conversations(
474476
claims=Depends(conditional_security),
475477
) -> ChatResponse:
476478
cognite_obo_token = None
477-
if settings.security.enabled and agent_factory.settings.tools.cognite.client_secret:
479+
if settings.security.enabled and agent_factory.settings.tools.cognite and agent_factory.settings.tools.cognite.client_secret:
478480
token_result = exchange_obo_for_cognite(authorization)
479481
cognite_obo_token = token_result["access_token"]
480482

src/talk2powersystemllm/app/trouble.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ Response Body JSON Schema:
6767
"clientId": {
6868
"type": "string"
6969
},
70+
"frontendAppClientId": {
71+
"type": "string"
72+
},
73+
"scopes": {
74+
"type": "array",
75+
"items": [
76+
{
77+
"type": "string"
78+
}
79+
]
80+
},
7081
"authority": {
7182
"type": "string"
7283
},
@@ -91,11 +102,17 @@ Sample Response Body:
91102
```json
92103
{
93104
"enabled": true,
94-
"clientId": "6f8c5e30-b4b4-4b78-bdba-0ac5f5947fb6",
105+
"clientId": "7b9f2087-68f1-45fe-a21d-023daedd4047",
106+
"frontendAppClientId": "10acd638-f239-4aa2-8186-761512253325",
107+
"scopes": [
108+
"openid",
109+
"profile",
110+
"api://7b9f2087-68f1-45fe-a21d-023daedd4047/.default"
111+
],
95112
"authority": "https://login.microsoftonline.com/519ed184-e4d5-4431-97e5-fb4410a3f875",
96-
"logout": "https://login.microsoftonline.com/519ed184-e4d5-4431-97e5-fb4410a3f875/oauth2/logout",
97-
"loginRedirect": "http://localhost:3000",
98-
"logoutRedirect": "http://localhost:3000/login"
113+
"logout": "https://login.microsoftonline.com/519ed184-e4d5-4431-97e5-fb4410a3f875/oauth2/v2.0/logout",
114+
"loginRedirect": "http://localhost:3000/",
115+
"logoutRedirect": "http://localhost:3000/"
99116
}
100117
```
101118

0 commit comments

Comments
 (0)