diff --git a/etc/nss_pam_oidc.example.toml b/etc/nss_pam_oidc.example.toml
index a1d62914bad27fce9c3dbd8fd3f6b5e95c9583c7..2b37ec5c90ed57515a89b8506f4bd71302a064d3 100644
--- a/etc/nss_pam_oidc.example.toml
+++ b/etc/nss_pam_oidc.example.toml
@@ -32,3 +32,6 @@ maps.passwd = """
         shell: .login_shell
     }
 """
+
+# Reverse mapping to make sure uid lookups on entries mapped above still work
+maps.rev.passwd.by_uid = ". - 10000"
diff --git a/src/nss.rs b/src/nss.rs
index b7bbad81b58d7eb3e7b497718158fe0bb09f8fca..dca4b9fd8a3f120426a30f705f33c2e0450c6167 100644
--- a/src/nss.rs
+++ b/src/nss.rs
@@ -119,7 +119,7 @@ impl PasswdHooks for OidcPasswd {
             }
         };
 
-        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid.to_string(), &token, false).map(|PasswdHelper(p)| p) {
+        let data: Passwd = match get_data_jq(&conf, "nss", "passwd.by_uid", uid, &token, false).map(|PasswdHelper(p)| p) {
             Ok(d) => d,
             Err(e) => {
                 error!("Could not load JSON data for passwd: {}", e);
diff --git a/src/oauth.rs b/src/oauth.rs
index 2e0a500cfd6c5b0df897a4c9fc87f54c3d1e41fa..72f3f625f1197e36925b6c1dc7b68c00be461dce 100644
--- a/src/oauth.rs
+++ b/src/oauth.rs
@@ -39,7 +39,7 @@ use oauth2::reqwest::http_client;
 
 use std::error;
 
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
 use reqwest;
 
 use serde_json;
@@ -151,15 +151,23 @@ fn get_data(conf: &Config, prefix: &str, endpoint: &str, param: String, token: &
         .text()?)
 }
 
-fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str) -> Option<String> {
-    match get_optional(&conf, &full_key(vec![prefix, "maps", endpoint])) {
+fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str, rev: bool) -> Option<String> {
+    let prog_key = match rev {
+        false => full_key(vec![prefix, "maps", endpoint]),
+        true => full_key(vec![prefix, "maps.rev", endpoint]),
+    };
+    match get_optional(&conf, &prog_key) {
         Some(v) => Some (v),
         None => {
+            if rev {
+                // Do not fallback to more generic program for reverse mapping
+                return None;
+            }
             // Try falling back to more generic program
             match endpoint.find('.') {
                 Some(i) => {
                     debug!("JQ mapping program for {} not found; trying more generic definition", endpoint);
-                    get_jq_prog(conf, prefix, &endpoint[..i])
+                    get_jq_prog(conf, prefix, &endpoint[..i], rev)
                 },
                 None => None
             }
@@ -167,11 +175,11 @@ fn get_jq_prog(conf: &Config, prefix: &str, endpoint: &str) -> Option<String> {
     }
 }
 
-pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, endpoint: &str, param: String, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> {
-    let res: Option<String> = get_jq_prog(&conf, prefix, endpoint);
+pub fn get_data_jq<T: for<'de> Deserialize<'de>, V: Serialize>(conf: &Config, prefix: &str, endpoint: &str, param: V, token: &BasicTokenResponse, multi: bool) -> Result<T, Box<dyn error::Error>> {
+    let res: Option<String> = get_jq_prog(&conf, prefix, endpoint, false);
     let jq_code = match res {
         Some(s) => {
-            debug!("Found jq mapping program for endpoint {}", endpoint);
+            debug!("Found jq mapping program for endpoint {}: {}", endpoint, s);
             match multi {
                 true => "map(".to_string() + &s + ")",
                 false => s
@@ -183,6 +191,30 @@ pub fn get_data_jq<T: for<'de> Deserialize<'de>>(conf: &Config, prefix: &str, en
         }
     };
     let mut jq_prog = jq_rs::compile(&jq_code)?;
+    let res: Option<String> = get_jq_prog(&conf, prefix, endpoint, true);
+    let jq_code = match res {
+        Some(s) => {
+            debug!("Found jq reverse mapping program for endpoint {}: {}", endpoint, s);
+            s
+        },
+        None => {
+            debug!("No jq reverse mapping program for endpoint {}; using default (no-op)", endpoint);
+            ".".to_string()
+        }
+    };
+    let mut jq_prog_rev = jq_rs::compile(&jq_code)?;
+
+    // Convert and transform the passed param using the reverse JQ mapping program
+    //  1. Serialize into JSON value (atomic) to be bale to pass into jq
+    let param_serialized = serde_json::to_string(&param)?;
+    //  2. Transform using the JQ program loaded above
+    let param_trans = jq_prog_rev.run(&param_serialized)?.trim().to_string();
+    //  3. Deserialize into serde_json value so we get numbers as numbers, strings properly unquoted
+    let param_deserialized: serde_json::Value = serde_json::from_str(&param_trans)?;
+    let param = match param_deserialized {
+        serde_json::Value::String(v) => v,   // We want strings verbatim without JSON quoting
+        _ => param_deserialized.to_string()  // We want numbers converted to string
+    };
 
     let data_raw = get_data(&conf, prefix, endpoint, param, token)?;
     let data_trans = jq_prog.run(&data_raw)?;