Zero Trust User Permissions

Hey yall, I have a Vue app that I expect max 10 users to use internally. I have two groups of users that I want to use this app. One group will have certain permissions within the app and the other group will not. This app is hosted on Cloudflare pages with Zero trust currently. Is there a way to communicate to the Vue app that certain emails or users (maybe with a worker?) have signed into using Zero trust so I can handle the the permissions that user should see on the Vue app?
3 Replies
b1ueh4wk
b1ueh4wkOP3mo ago
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
// Step 1: Get the JWT token from the request headers
const jwtToken = request.headers.get('Cf-Access-Jwt-Assertion')
let userEmail = null
if (jwtToken) {
// Step 2: Decode the JWT token and extract the email
userEmail = getEmailFromJwt(jwtToken)
}
// Step 3: Forward the request to the origin server (your Vue app)
const response = await fetch(request)
// Check if the response is valid
if (!response) {
return new Response('Error fetching the origin response', { status: 502 })
}
// Step 4: Create a new Headers object from the response headers
let newHeaders = new Headers(response.headers)

// Step 5: Determine isAdmin value based on email
let isAdmin = "false"
if (userEmail === '[email protected]' || userEmail === '[email protected]') {
isAdmin = "true"
}

// Step 6: Set the isAdmin header
newHeaders.set('isAdmin', isAdmin)

// Step 7: Create a new Response with the modified headers
let newResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
})

return newResponse
}

// Helper function to decode JWT and extract the email
function getEmailFromJwt(token) {
try {
// Split the token into header, payload, and signature
const base64Url = token.split('.')[1]
const base64 = base64UrlReplace(base64Url)
const jsonPayload = atob(base64)
const payload = JSON.parse(jsonPayload)
return payload.email
} catch (e) {
console.error('Error decoding JWT:', e)
return null
}
}
// Helper function to replace URL-safe base64 characters
function base64UrlReplace(base64Url) {
// Replace '-' with '+' and '_' with '/'
base64Url = base64Url.replace(/-/g, '+').replace(/_/g, '/')
// Pad with '='
while (base64Url.length % 4) {
base64Url += '='
}
return base64Url
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
// Step 1: Get the JWT token from the request headers
const jwtToken = request.headers.get('Cf-Access-Jwt-Assertion')
let userEmail = null
if (jwtToken) {
// Step 2: Decode the JWT token and extract the email
userEmail = getEmailFromJwt(jwtToken)
}
// Step 3: Forward the request to the origin server (your Vue app)
const response = await fetch(request)
// Check if the response is valid
if (!response) {
return new Response('Error fetching the origin response', { status: 502 })
}
// Step 4: Create a new Headers object from the response headers
let newHeaders = new Headers(response.headers)

// Step 5: Determine isAdmin value based on email
let isAdmin = "false"
if (userEmail === '[email protected]' || userEmail === '[email protected]') {
isAdmin = "true"
}

// Step 6: Set the isAdmin header
newHeaders.set('isAdmin', isAdmin)

// Step 7: Create a new Response with the modified headers
let newResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
})

return newResponse
}

// Helper function to decode JWT and extract the email
function getEmailFromJwt(token) {
try {
// Split the token into header, payload, and signature
const base64Url = token.split('.')[1]
const base64 = base64UrlReplace(base64Url)
const jsonPayload = atob(base64)
const payload = JSON.parse(jsonPayload)
return payload.email
} catch (e) {
console.error('Error decoding JWT:', e)
return null
}
}
// Helper function to replace URL-safe base64 characters
function base64UrlReplace(base64Url) {
// Replace '-' with '+' and '_' with '/'
base64Url = base64Url.replace(/-/g, '+').replace(/_/g, '/')
// Pad with '='
while (base64Url.length % 4) {
base64Url += '='
}
return base64Url
}
Figured out a solution: Created a worker that works with that Cloudflare page specifically (Click on worker, Go to Settings, Go to Triggers, Add Route). Then I added the javascript seen above ^ This creates a new header on the request, and I can search for that value in my Vue application to display the different permissions.
Chaika
Chaika2mo ago
Very nice! only bit I think you're missing is actually verifying the JWT: https://developers.cloudflare.com/cloudflare-one/identity/authorization-cookie/validating-json/ and not just trusting whatever came through. There has been exploits in the past where CF Access was skipped/issues like that, I'm sure CF has taken those more seriously since then but still a potential worry/edge case that may be worth covering if you're using it to assign admin status
Cloudflare Docs
Validate JWTs | Cloudflare Zero Trust docs
When Cloudflare sends a request to your origin, the request will include an application token as a Cf-Access-Jwt-Assertion request header and as a CF_Authorization cookie.
b1ueh4wk
b1ueh4wkOP2mo ago
Oh thank you very much for this information @Chaika ! I will have to look into this.
Want results from more Discord servers?
Add your server