Skip to content

Commit 34cb254

Browse files
authored
JWT auth added for MCP server's api calls (#314)
* JWT auth added for MCP server's api calls
1 parent ef5f603 commit 34cb254

File tree

11 files changed

+68
-94
lines changed

11 files changed

+68
-94
lines changed

deploy/docker/docker-compose.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ services:
156156
environment:
157157
- TLS_ENABLED=${TLS_ENABLED:-false}
158158
- SERVER_PORT=${CHATBOT_SERVER_PORT:-5002}
159+
- WEB_SERVICE=crapi-web
160+
- IDENTITY_SERVICE=crapi-identity:${IDENTITY_SERVER_PORT:-8080}
159161
- DB_NAME=crapi
160162
- DB_USER=admin
161163
- DB_PASSWORD=crapisecretpassword
@@ -166,6 +168,9 @@ services:
166168
- MONGO_DB_USER=admin
167169
- MONGO_DB_PASSWORD=crapisecretpassword
168170
- MONGO_DB_NAME=crapi
171+
172+
- API_PASSWORD=Admin!123
173+
- OPENAPI_SPEC=/app/resources/crapi-openapi-spec.json
169174
- DEFAULT_MODEL=gpt-4o-mini
170175
- CHROMA_PERSIST_DIRECTORY=/app/vectorstore
171176
# - CHATBOT_OPENAI_API_KEY=

deploy/helm/templates/chatbot/config.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ metadata:
88
data:
99
SERVER_PORT: {{ .Values.chatbot.port | quote }}
1010
IDENTITY_SERVICE: {{ .Values.identity.service.name }}:{{ .Values.identity.port }}
11-
WEB_SERVICE: {{ .Values.web.service.name }}:{{ .Values.web.port }}
11+
WEB_SERVICE: {{ .Values.web.service.name }}
1212
TLS_ENABLED: {{ .Values.tlsEnabled | quote }}
1313
DB_HOST: {{ .Values.postgresdb.service.name }}
1414
DB_USER: {{ .Values.postgresdb.config.postgresUser }}
@@ -23,3 +23,6 @@ data:
2323
CHATBOT_OPENAI_API_KEY: {{ .Values.openAIApiKey }}
2424
DEFAULT_MODEL: {{ .Values.chatbot.config.defaultModel | quote }}
2525
CHROMA_PERSIST_DIRECTORY: {{ .Values.chatbot.config.chromaPersistDirectory | quote }}
26+
API_USER: {{ .Values.chatbot.config.apiUser | quote }}
27+
API_PASSWORD: {{ .Values.chatbot.config.apiPassword | quote }}
28+
OPENAPI_SPEC: {{ .Values.chatbot.config.openapiSpec | quote }}

deploy/helm/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ chatbot:
153153
secretKey: crapi
154154
defaultModel: gpt-4o-mini
155155
chromaPersistDirectory: /app/vectorstore
156+
157+
apiPassword: Admin!123
158+
openapiSpec: /app/resources/crapi-openapi-spec.json
156159
storage:
157160
# type: "manual"
158161
# pv:
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
from dotenv import load_dotenv
3+
4+
load_dotenv()
5+
6+
class Config:
7+
TLS_ENABLED = os.getenv("TLS_ENABLED", "false").lower() in ("true", "1", "yes")
8+
WEB_SERVICE = os.getenv("WEB_SERVICE", "crapi-web")
9+
IDENTITY_SERVICE = os.getenv("IDENTITY_SERVICE", "crapi-identity:8080")
10+
CHROMA_PERSIST_DIRECTORY = os.getenv("CHROMA_PERSIST_DIRECTORY", "/app/vectorstore")
11+
OPENAPI_SPEC = os.getenv("OPENAPI_SPEC", "/app/resources/crapi-openapi-spec.json")
12+
API_USER = os.getenv("API_USER", "[email protected]")
13+
API_PASSWORD = os.getenv("API_PASSWORD", "Admin!123")

services/chatbot/src/mcpserver/server.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from fastmcp import FastMCP, settings
33
import json
44
import os
5+
from .config import Config
56
import logging
67
import time
78
from .tool_helpers import (
@@ -15,36 +16,27 @@
1516
logger = logging.getLogger(__name__)
1617
logger.setLevel(logging.DEBUG)
1718

18-
WEB_SERVICE = os.environ.get("WEB_SERVICE", "crapi-web")
19-
IDENTITY_SERVICE = os.environ.get("IDENTITY_SERVICE", "crapi-identity:8080")
20-
TLS_ENABLED = os.environ.get("TLS_ENABLED", "false").lower() in ("true", "1", "yes")
21-
BASE_URL = f"{'https' if TLS_ENABLED else 'http'}://{WEB_SERVICE}"
22-
BASE_IDENTITY_URL = f"{'https' if TLS_ENABLED else 'http'}://{IDENTITY_SERVICE}"
23-
24-
API_USER = os.environ.get("API_USER", "[email protected]")
25-
API_PASSWORD = os.environ.get("API_PASSWORD", "Admin!123")
26-
API_URL = f"{'https' if TLS_ENABLED else 'http'}://{WEB_SERVICE}"
27-
19+
BASE_URL = f"{'https' if Config.TLS_ENABLED else 'http'}://{Config.WEB_SERVICE}"
20+
BASE_IDENTITY_URL = f"{'https' if Config.TLS_ENABLED else 'http'}://{Config.IDENTITY_SERVICE}"
2821
API_KEY = None
29-
API_AUTH_TYPE = "ApiKey"
3022

3123
def get_api_key():
3224
global API_KEY
33-
# Try 5 times to get API key
25+
# Try 5 times to get client auth
3426
MAX_ATTEMPTS = 5
3527
for i in range(MAX_ATTEMPTS):
3628
logger.info(f"Attempt {i+1} to get API key...")
3729
if API_KEY is None:
38-
login_body = {"email": API_USER, "password": API_PASSWORD}
39-
apikey_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey"
30+
login_body = {"email": Config.API_USER, "password": Config.API_PASSWORD}
31+
auth_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey"
4032
headers = {
4133
"Content-Type": "application/json",
4234
}
4335
with httpx.Client(
44-
base_url=API_URL,
36+
base_url=BASE_URL,
4537
headers=headers,
4638
) as client:
47-
response = client.post(apikey_url, json=login_body)
39+
response = client.post(auth_url, json=login_body)
4840
if response.status_code != 200:
4941
if i == MAX_ATTEMPTS - 1:
5042
logger.error(f"Failed to get API key after {i+1} attempts: {response.status_code} {response.text}")
@@ -54,24 +46,23 @@ def get_api_key():
5446
response_json = response.json()
5547
logger.info(f"Response: {response_json}")
5648
API_KEY = response_json.get("apiKey")
57-
logger.info(f"Chatbot API Key: {API_KEY}")
49+
logger.info(f"MCP Server API Key: {API_KEY}")
5850
return API_KEY
5951
return API_KEY
6052

61-
6253
# Async HTTP client for API calls
6354
def get_http_client():
6455
"""Create and configure the HTTP client with appropriate authentication."""
6556
headers = {
6657
"Authorization": "ApiKey " + get_api_key(),
6758
}
6859
return httpx.AsyncClient(
69-
base_url=API_URL,
60+
base_url=BASE_URL,
7061
headers=headers,
7162
)
7263

7364
# Load your OpenAPI spec
74-
with open("/app/resources/crapi-openapi-spec.json", "r") as f:
65+
with open(Config.OPENAPI_SPEC, "r") as f:
7566
openapi_spec = json.load(f)
7667

7768
# Create the MCP server

services/chatbot/src/mcpserver/tool_helpers.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
from langchain_community.vectorstores import Chroma
44
from langchain.prompts import PromptTemplate
55
from chatbot.extensions import db
6-
from chatbot.config import Config
6+
from .config import Config
77
from langchain.chains import RetrievalQA
88
from langchain_openai import ChatOpenAI
99

10-
retrieval_index_path = "/app/resources/chat_index"
11-
1210
async def get_any_api_key():
1311
if os.environ.get("CHATBOT_OPENAI_API_KEY"):
1412
return os.environ.get("CHATBOT_OPENAI_API_KEY")

services/identity/src/main/java/com/crapi/config/JwtAuthTokenFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public String getUserFromToken(HttpServletRequest request) throws ParseException
121121
if (token != null) {
122122
if (apiType == ApiType.APIKEY) {
123123
log.debug("Token is api token");
124-
username = tokenProvider.getUserNameFromApiToken(token);
124+
username = tokenProvider.getUserNameFromJwtToken(token);
125125
} else {
126126
log.debug("Token is jwt token");
127127
if (tokenProvider.validateJwtToken(token)) {

services/identity/src/main/java/com/crapi/config/JwtProvider.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,27 +103,27 @@ public String generateJwtToken(User user) {
103103
}
104104

105105
/**
106-
* @param token
107-
* @return username from JWT Token
106+
* @param user
107+
* @return generated apikey token without expiry date
108108
*/
109-
public String getUserNameFromJwtToken(String token) throws ParseException {
110-
// Parse without verifying token signature
111-
return JWTParser.parse(token).getJWTClaimsSet().getSubject();
109+
public String generateApiKey(User user) {
110+
JwtBuilder builder =
111+
Jwts.builder()
112+
.subject(user.getEmail())
113+
.issuedAt(new Date())
114+
.claim("role", user.getRole().getName())
115+
.signWith(this.keyPair.getPrivate());
116+
String jwt = builder.compact();
117+
return jwt;
112118
}
113119

114120
/**
115121
* @param token
116122
* @return username from JWT Token
117123
*/
118-
public String getUserNameFromApiToken(String token) throws ParseException {
124+
public String getUserNameFromJwtToken(String token) throws ParseException {
119125
// Parse without verifying token signature
120-
if (token != null) {
121-
User user = userRepository.findByApiKey(token);
122-
if (user != null) {
123-
return user.getEmail();
124-
}
125-
}
126-
return null;
126+
return JWTParser.parse(token).getJWTClaimsSet().getSubject();
127127
}
128128

129129
// Load RSA Public Key for JKU header if present

services/identity/src/main/java/com/crapi/service/Impl/UserServiceImpl.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import com.crapi.model.*;
2626
import com.crapi.repository.*;
2727
import com.crapi.service.UserService;
28-
import com.crapi.utils.ApiKeyGenerator;
2928
import com.crapi.utils.EmailTokenGenerator;
3029
import com.crapi.utils.MailBody;
3130
import com.crapi.utils.OTPGenerator;
@@ -469,19 +468,17 @@ public JwtResponse unlockAccount(
469468
@Override
470469
@Transactional
471470
public ApiKeyResponse generateApiKey(HttpServletRequest request, LoginForm loginForm) {
472-
// if user is unauthenticated, use loginForm else user token to authenticate
471+
Authentication authentication =
472+
authenticationManager.authenticate(
473+
new UsernamePasswordAuthenticationToken(loginForm.getEmail(), loginForm.getPassword()));
474+
if (authentication == null) {
475+
return new ApiKeyResponse(null, UserMessage.INVALID_CREDENTIALS);
476+
}
477+
log.info("Generate Api Key for user: {}", loginForm.getEmail());
473478
User user;
474479
if (request == null || jwtAuthTokenFilter.getToken(request) == null) {
475480
user = userRepository.findByEmail(loginForm.getEmail());
476481
} else {
477-
log.info("Generate Api Key for user: {}", loginForm.getEmail());
478-
Authentication authentication =
479-
authenticationManager.authenticate(
480-
new UsernamePasswordAuthenticationToken(
481-
loginForm.getEmail(), loginForm.getPassword()));
482-
if (authentication == null) {
483-
return new ApiKeyResponse(null, UserMessage.INVALID_CREDENTIALS);
484-
}
485482
user = getUserFromToken(request);
486483
}
487484
if (user == null) {
@@ -493,11 +490,13 @@ public ApiKeyResponse generateApiKey(HttpServletRequest request, LoginForm login
493490
log.debug("Api Key already generated for user: {}", user.getEmail());
494491
return new ApiKeyResponse(user.getApiKey());
495492
}
496-
log.info("Generate Api Key for user in token: {}", user.getEmail());
497-
String apiKey = ApiKeyGenerator.generateRandom(512);
498-
log.debug("Api Key for user in token {}: {}", user.getEmail(), apiKey);
493+
String apiKey = jwtProvider.generateApiKey(user);
494+
log.debug("Api Key for user {}: {}", user.getEmail(), apiKey);
495+
if (apiKey == null) {
496+
return new ApiKeyResponse(null, UserMessage.API_KEY_GENERATION_FAILED);
497+
}
499498
user.setApiKey(apiKey);
500-
userRepository.save(user);
499+
userRepository.saveAndFlush(user);
501500
return new ApiKeyResponse(user.getApiKey(), UserMessage.API_KEY_GENERATED_MESSAGE);
502501
}
503502

services/identity/src/main/java/com/crapi/utils/ApiKeyGenerator.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)