竹影流浪

竹影流浪

Tauri+Better Auth的跨域问题

2025-08-24

问题说明

如果是普通网页中的Better Auth那只需要前后端部署在同一域名下即可, 例如使用nginx部署前端+反向代理后端; 如果是SSR那本身就是全栈不存在跨域问题, 如果是原生应用则没有CORS跨域问题, 只需要考虑cookie兼容即可。 只有Tauri这种环境下会比较麻烦, 它无法像Electron一样轻易的关闭跨域校验, 所以一般从服务端下手。

允许跨域

如果后端直接暴露端点, 那么应该在应用层解决跨域问题, 手动添加Access-Control-Allow-*一系列响应头。例如搜索Express允许跨域Fastify允许跨域关键字, 当然还一些一点需要注意, 建议看完全文。

或者在一般情况下为了复用443端口会在套一层Nginx, 在这种情况下就应该在Nginx中配置跨域。

OpenResty 配置

access_by_lua_block {
    local allowed_origins = {
        ["http://tauri.localhost"] = true,
        ["http://localhost:14200"] = true
    }
    local origin = ngx.var.http_origin
    if origin and allowed_origins[origin] then
        ngx.header["Access-Control-Allow-Origin"] = origin
        ngx.header["Access-Control-Allow-Credentials"] = "true"
        ngx.header["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE"
        ngx.header["Access-Control-Allow-Headers"] = "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
        ngx.header["Access-Control-Max-Age"] = 1728000
    end
    if ngx.req.get_method() == "OPTIONS" and origin and allowed_origins[origin] then
        ngx.exit(ngx.HTTP_NO_CONTENT)
    end
}

不过由于一些面板(例如1panel)的WAF和网站监控功能依赖于access_by_lua_file, 而access_by_lua_blockaccess_by_lua_file是冲突,两者都存在时只会生效其中一项, 因此这种情况需要使用Nginx配置

Nginx 配置

server外部添加map

# 当请求来源在白名单内时,$cors_origin 的值为来源地址,否则为空字符串
map $http_origin $cors_origin {
    default                           "";
    "http://tauri.localhost"          $http_origin;
    "http://localhost:14200"          $http_origin;
}

# 当请求来源在白名单内时,$cors_creds 的值为 "true",否则为空字符串
map $http_origin $cors_creds {
    default                           "";
    "http://tauri.localhost"          "true";
    "http://localhost:14200"          "true";
}

location内添加配置

    set $cors_request "";
    if ($request_method = 'OPTIONS') {
        set $cors_request "${cors_request}O";
    }
    if ($cors_origin != "") {
        set $cors_request "${cors_request}V";
    }
    if ($cors_request = "OV") {
        add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
        add_header 'Access-control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
        add_header 'Access-Control-Max-Age' 1728000 always;
        return 204;
    }
    if ($cors_request = "O") {
        return 403;
    }
    if ($cors_request ~ "V") {
        add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
        add_header 'Access-Control-Allow-Credentials' "$cors_creds" always;
    }

总结下来可能是这样:

# 当请求来源在白名单内时,$cors_origin 的值为来源地址,否则为空字符串
map $http_origin $cors_origin {
    default                           "";
    "http://tauri.localhost"          $http_origin;
    "http://localhost:14200"          $http_origin;
}

# 当请求来源在白名单内时,$cors_creds 的值为 "true",否则为空字符串
map $http_origin $cors_creds {
    default                           "";
    "http://tauri.localhost"          "true";
    "http://localhost:14200"          "true";
}

server {
    listen 80 ; 
    server_name example.com;
    location ^~ / {
        set $cors_request "";
        if ($request_method = 'OPTIONS') {
            set $cors_request "${cors_request}O";
        }
        if ($cors_origin != "") {
            set $cors_request "${cors_request}V";
        }
        if ($cors_request = "OV") {
            add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
            add_header 'Access-control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
            add_header 'Access-Control-Max-Age' 1728000 always;
            return 204;
        }
        if ($cors_request = "O") {
            return 403;
        }
        if ($cors_request ~ "V") {
            add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
            add_header 'Access-Control-Allow-Credentials' "$cors_creds" always;
        }
        proxy_pass http://127.0.0.1:8080;
    }
}

因为nginx不支持嵌套if, 所以看起来会有OV这样的奇怪判断逻辑。

跨域配置注意事项

上述Access-Control-Allow-Origin不能直接设置为*, 因为在Access-Control-Allow-Credentialstrue的情况下浏览器为了安全要求(防止CSRF攻击)Access-Control-Allow-Origin必须设置为具体值。

这里设置的http://tauri.localhosthttp://localhost:14200Tauri在生产和开发环境下的Origin

跨域配置总结

Access-Control-Allow-Origin是允许浏览器发起跨域请求
Access-Control-Allow-Credentials是允许浏览器发起跨域请求时携带cookie

allowed_origins保证了只有tauri环境下才允许跨域

Better Auth配置

trustedorigins配置

Better Auth中还有一个 trustedorigins配置, 该配置也是为了防止CSRF攻击。

上面的OpenResty配置作用在浏览器层面, 下述Better Auth配置作用在应用层层面, 因此这里也要进行配置, 否则调用Better Auth接口时会报403:

{
  trustedOrigins: [
    'http://localhost:14200',
    'http://tauri.localhost',
  ],
}

samesite配置

Better Auth默认情况下cookie设置的 samesiteLax, 在该配置也是在防止CSRF攻击, 该配置下Tauri中发起的请求不会携带Cookie, 所以还需要配置:

{
  advanced: {
    defaultCookieAttributes: {
      sameSite: 'none',
      secure: true,
      partitioned: true,
    },
  },
}

合并后配置如下:

export const auth = betterAuth({
  trustedOrigins: [
    'http://localhost:14200',
    'http://tauri.localhost',
  ],
  advanced: {
    defaultCookieAttributes: {
      sameSite: 'none',
      secure: true,
      partitioned: true,
    },
  },
})

总结

首先在OpenResty中配置服务器在Tauri环境下允许跨域请求, 请求时允许发送Cookie, 但是是否真的能够自动携带Cookie发起请求还受制于samesite配置, 最后应用层也会校验Origin, 因此还需要配置trustedorigins