Overcoming Same-Origin Policy Challenges: A Guide to Secure microservices having Cross-Domain Authentication. Moving from URLs to Secure Headers - Part 2
How can I securely transmit JWT between microservices across different domains without exposing the token in the URL? What secure mechanisms can I use to enable cross-domain authentication?
The Problem
Problem #1: The previous method sends the JWT as a URL parameter, which is not secure. This can cause security issues like leaking the token.
Problem #2: My microservices are on two different domains, so I can't create cross-domain cookies directly due to browser security rules.
Problem #3: The Same-Origin Policy (SOP) limits interactions between different domains to prevent security threats like Cross-Site Request Forgery (CSRF) and Cross-Site Scripting (XSS).
Problem #4: Browser security features restrict direct interaction between different domains to safeguard user data.
The Solution
Solution #1: Use HTTP-only cookies or Authorization headers to transmit the JWT securely.
Solution #2: Implement a secure mechanism for cross-domain authentication that complies with the Same-Origin Policy (SOP), such as using a backend proxy or OAuth flows.
Solution #3: Utilize secure communication methods and proper authentication protocols to ensure compliance with SOP while maintaining functionality.
Solution #4: Implement secure cross-domain communication techniques, such as CORS (Cross-Origin Resource Sharing) with proper configurations, to enable necessary interactions while maintaining security.
Implementation
User Initiates Login: The process begins when a user visits the public service and clicks on the "Login with Google" link.
<a href="/login">Login with Google</a>
User Clicks the Login Link - URL:
https://public-service-183061022621.us-central1.run.app/login
Redirect to Google OAuth:
https://accounts.google.com/o/oauth2/v2/auth?client_id=183061022621-oiivmgvihfrnr14068aunhurm
Redirect to Google Sign-In: The /login
route in the public service redirects the user to Google's authentication page. This code constructs the Google authentication URL with necessary parameters like client_id, redirect_uri, and requested scopes.
@app.route('/login')
def login():
google_auth_url = "https://accounts.google.com/o/oauth2/v2/auth"
params = {
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": "openid email profile",
}
return redirect(f"{google_auth_url}?{urlencode(params)}")
User Authenticates with Google: The user enters their Google credentials on Google's login page.
Google Redirects Back with Authorization Code: After successful authentication, Google redirects the user back to the specified REDIRECT_URI with an authorization code.
https://public-service-183061022621.us-central1.run.app/callback?code=4%2F0AVG7fiQjyA3LSj8xvmlXOYaEVnZe2K3SoGQgaKCmCyQbfcBGhO-Dd7Q8aljNayxttQQo1w&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&hd=us.mcd.com&prompt=none
Exchange Authorization Code for Tokens: The public service receives the authorization code and exchanges it for access and ID tokens from Google.
URL: https://public-service-183061022621.us-central1.run.app/callback?code=4%2F0AVG7fiQjyA3LSj8xvmlXOYaEVnZe2K3SoGQgaKCmCyQbfcBGhO-Dd7Q8aljNayxttQQo1w
Public service exchanges the code for tokens:
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
client_id=183061022621-oiivmgvihfrnr14068aunhurmvkm5pcq.apps.googleusercontent.com&client_secret=GOCSPX-ncP
This code sends a POST request to Google's token endpoint to exchange the authorization code for tokens.
@app.route('/callback')
def callback():
code = request.args.get('code')
token_url = "https://oauth2.googleapis.com/token"
params = {
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": code,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code",
}
response = requests.post(token_url, data=params)
token_info = response.json()
id_token = token_info.get("id_token")
Retrieve User Information: The public service uses the ID token to fetch the user's email from Google
user_info = requests.get(f"https://oauth2.googleapis.com/tokeninfo?id_token={id_token}").json()
email = user_info.get('email')
Create JWT: The public service creates a JSON Web Token (JWT) containing the user's email.
jwt_token = jwt.encode({'email': email}, JWT_SECRET_KEY, algorithm='HS256')
Public service returns
redirect.html.
Redirect to Private Service: The public service sends the JWT to the client-side JavaScript, which then makes a request to the private service.
Client-Side Request to Private Service: The JavaScript code sends a request to the private service with the JWT in the Authorization header.
Private Service Verifies JWT: The private service receives the request, extracts the JWT from the Authorization header, and verifies it.
@app.route('/')
def private():
auth_header = request.headers.get('Authorization')
token = auth_header.split(' ')[1]
try:
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
email = payload.get('email')
return render_template('index.html', email=email)
except jwt.InvalidTokenError:
return render_template('index.html', email="Invalid token"), 401
Private Service Responds: If the JWT is valid, the private service responds with the protected content.
The Concepts
1️⃣Access-Control-Allow-Origin
Purpose: This header is used to specify which origins are allowed to access the resource. It is part of the CORS (Cross-Origin Resource Sharing) mechanism.
Usage: This header is set by the server to indicate that the resource can be accessed by the specified origin.
2️⃣Content-Security-Policy (CSP)
Purpose: This header is used to prevent various types of attacks, such as Cross-Site Scripting (XSS) and data injection attacks, by specifying which sources of content are trusted.
Usage: This header specifies the sources from which content can be loaded. In this example, only content from the same origin (
'self'
) and the specified private service domain is allowed.
3️⃣Strict-Transport-Security (HSTS)
Purpose: This header is used to enforce secure (HTTPS) connections to the server.
Usage: This header tells the browser to only connect to the server over HTTPS for the specified duration (
max-age
). TheincludeSubDomains
directive applies the policy to all subdomains, and thepreload
directive indicates that the site should be included in the browser's HSTS preload list.
4️⃣X-Content-Type-Options
Purpose: This header is used to prevent the browser from interpreting files as a different MIME type than declared by the server.
Usage: This header prevents the browser from MIME-sniffing, which can be used to execute scripts in unexpected contexts.
5️⃣X-Frame-Options
Purpose: This header is used to control whether a browser should be allowed to render a page in a frame, iframe, or object.
Usage: This header prevents clickjacking attacks by disallowing the page to be rendered in a frame.
6️⃣X-XSS-Protection
Purpose: This header is used to enable the Cross-Site Scripting (XSS) filter in most recent web browsers.
Usage: This header enables the XSS filter and specifies that the browser should block the response if an XSS attack is detected.
7️⃣Referrer-Policy
Purpose: This header is used to control how much referrer information (sent with the
Referer
header) should be included with requests.Usage: This header specifies that no referrer information should be sent with requests.