[{"data":1,"prerenderedAt":1318},["ShallowReactive",2],{"navigation_docs":3,"-docs-device-and-challenges":110,"-docs-device-and-challenges-surround":1313},[4,8,12,16,20,24,28,32,36,40,44,82,86,90,94,98,102,106],{"title":5,"path":6,"stem":7},"Overview","\u002Fdocs","docs\u002Findex",{"title":9,"path":10,"stem":11},"Quickstart","\u002Fdocs\u002Fquickstart","docs\u002F01.quickstart",{"title":13,"path":14,"stem":15},"Authentication and keys","\u002Fdocs\u002Fauthentication","docs\u002F02.authentication",{"title":17,"path":18,"stem":19},"Images","\u002Fdocs\u002Fimages","docs\u002F03.images",{"title":21,"path":22,"stem":23},"Texts","\u002Fdocs\u002Ftexts","docs\u002F04.texts",{"title":25,"path":26,"stem":27},"Migrating from SightEngine","\u002Fdocs\u002Fmigrating-from-sightengine","docs\u002F05.migrating-from-sightengine",{"title":29,"path":30,"stem":31},"Workflows","\u002Fdocs\u002Fworkflows","docs\u002F06.workflows",{"title":33,"path":34,"stem":35},"Responses and errors","\u002Fdocs\u002Fresponses-and-errors","docs\u002F07.responses-and-errors",{"title":37,"path":38,"stem":39},"Limits and quotas","\u002Fdocs\u002Flimits-and-quotas","docs\u002F08.limits-and-quotas",{"title":41,"path":42,"stem":43},"The journal and ref","\u002Fdocs\u002Fjournal-and-ref","docs\u002F09.journal-and-ref",{"title":45,"path":46,"stem":47,"children":48,"page":81},"Integration guides","\u002Fdocs\u002Fintegration-guides","docs\u002F10.integration-guides",[49,53,57,61,65,69,73,77],{"title":50,"path":51,"stem":52},"Node.js and TypeScript","\u002Fdocs\u002Fintegration-guides\u002Fnodejs","docs\u002F10.integration-guides\u002F1.nodejs",{"title":54,"path":55,"stem":56},"Python","\u002Fdocs\u002Fintegration-guides\u002Fpython","docs\u002F10.integration-guides\u002F2.python",{"title":58,"path":59,"stem":60},"PHP","\u002Fdocs\u002Fintegration-guides\u002Fphp","docs\u002F10.integration-guides\u002F3.php",{"title":62,"path":63,"stem":64},"Go","\u002Fdocs\u002Fintegration-guides\u002Fgo","docs\u002F10.integration-guides\u002F4.go",{"title":66,"path":67,"stem":68},"Supabase Edge Functions","\u002Fdocs\u002Fintegration-guides\u002Fsupabase-edge-functions","docs\u002F10.integration-guides\u002F5.supabase-edge-functions",{"title":70,"path":71,"stem":72},"Cloudflare Workers","\u002Fdocs\u002Fintegration-guides\u002Fcloudflare-workers","docs\u002F10.integration-guides\u002F6.cloudflare-workers",{"title":74,"path":75,"stem":76},"Nuxt and Next","\u002Fdocs\u002Fintegration-guides\u002Fnuxt-and-next","docs\u002F10.integration-guides\u002F7.nuxt-and-next",{"title":78,"path":79,"stem":80},"Moderate an AI agent","\u002Fdocs\u002Fintegration-guides\u002Fmoderate-ai-agents","docs\u002F10.integration-guides\u002F8.moderate-ai-agents",false,{"title":83,"path":84,"stem":85},"AI coding agents","\u002Fdocs\u002Fai-agents","docs\u002F11.ai-agents",{"title":87,"path":88,"stem":89},"Best practices","\u002Fdocs\u002Fbest-practices","docs\u002F12.best-practices",{"title":91,"path":92,"stem":93},"Privacy and data","\u002Fdocs\u002Fprivacy-and-data","docs\u002F13.privacy-and-data",{"title":95,"path":96,"stem":97},"API reference","\u002Fdocs\u002Fapi-reference","docs\u002F14.api-reference",{"title":99,"path":100,"stem":101},"Fraud prevention","\u002Fdocs\u002Ffraud-prevention","docs\u002F16.fraud-prevention",{"title":103,"path":104,"stem":105},"Device token and challenges","\u002Fdocs\u002Fdevice-and-challenges","docs\u002F17.device-and-challenges",{"title":107,"path":108,"stem":109},"Changelog","\u002Fdocs\u002Fchangelog","docs\u002F99.changelog",{"id":111,"title":103,"body":112,"description":1305,"extension":1306,"links":1307,"meta":1308,"navigation":371,"path":104,"seo":1309,"stem":105,"__hash__":1312},"docs\u002Fdocs\u002F17.device-and-challenges.md",{"type":113,"value":114,"toc":1296},"minimark",[115,134,139,146,245,255,272,275,308,323,327,338,501,636,639,643,649,744,818,821,828,849,853,859,892,1180,1196,1208,1211,1264,1275,1279,1292],[116,117,118,119,123,124,128,129,133],"p",{},"The ",[120,121,122],"a",{"href":100},"fraud prevention API"," reads more than the fields your server sends. A small script on your pages gives each sensitive action a fresh ",[125,126,127],"strong",{},"device token",", and when a decision asks for proof (",[130,131,132],"code",{},"step_up","), you tell your instance how the proof went. This page covers both.",[135,136,138],"h2",{"id":137},"include-the-script","Include the script",[116,140,141,142,145],{},"openshield serves the script, a copy of openfraud's ",[130,143,144],{},"sdk\u002Fdevice\u002Fdevice.js"," at version 0.10.0, from its own address. Every version has a path of its own that never changes:",[147,148,153],"pre",{"className":149,"code":150,"language":151,"meta":152,"style":152},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript\n  type=\"module\"\n  src=\"https:\u002F\u002Fopenshield.dev\u002Fsdk\u002Fdevice\u002Fv0.10.0\u002Fdevice.js\"\n  integrity=\"sha384-my0AS6e8mdAdZ2QK46oa1ECvbPpPt2QTxiSVeBYFaLmhi+Pa9lAomhJ4WpE0jxLK\"\n  crossorigin=\"anonymous\"\n>\u003C\u002Fscript>\n","html","",[130,154,155,168,188,203,218,233],{"__ignoreMap":152},[156,157,160,164],"span",{"class":158,"line":159},"line",1,[156,161,163],{"class":162},"sMK4o","\u003C",[156,165,167],{"class":166},"swJcz","script\n",[156,169,171,175,178,181,185],{"class":158,"line":170},2,[156,172,174],{"class":173},"spNyl","  type",[156,176,177],{"class":162},"=",[156,179,180],{"class":162},"\"",[156,182,184],{"class":183},"sfazB","module",[156,186,187],{"class":162},"\"\n",[156,189,191,194,196,198,201],{"class":158,"line":190},3,[156,192,193],{"class":173},"  src",[156,195,177],{"class":162},[156,197,180],{"class":162},[156,199,200],{"class":183},"https:\u002F\u002Fopenshield.dev\u002Fsdk\u002Fdevice\u002Fv0.10.0\u002Fdevice.js",[156,202,187],{"class":162},[156,204,206,209,211,213,216],{"class":158,"line":205},4,[156,207,208],{"class":173},"  integrity",[156,210,177],{"class":162},[156,212,180],{"class":162},[156,214,215],{"class":183},"sha384-my0AS6e8mdAdZ2QK46oa1ECvbPpPt2QTxiSVeBYFaLmhi+Pa9lAomhJ4WpE0jxLK",[156,217,187],{"class":162},[156,219,221,224,226,228,231],{"class":158,"line":220},5,[156,222,223],{"class":173},"  crossorigin",[156,225,177],{"class":162},[156,227,180],{"class":162},[156,229,230],{"class":183},"anonymous",[156,232,187],{"class":162},[156,234,236,239,242],{"class":158,"line":235},6,[156,237,238],{"class":162},">\u003C\u002F",[156,240,241],{"class":166},"script",[156,243,244],{"class":162},">\n",[116,246,118,247,250,251,254],{},[130,248,249],{},"integrity"," attribute is the Subresource Integrity hash of that exact file: the browser refuses a script that differs by one byte. A versioned file is served with ",[130,252,253],{},"cache-control: public, max-age=31536000, immutable"," and with CORS open, so the hash can be checked from your site.",[116,256,257,260,261,264,265,268,269,271],{},[130,258,259],{},"https:\u002F\u002Fopenshield.dev\u002Fsdk\u002Fdevice\u002Flatest\u002Fdevice.js"," redirects to the newest version. It answers a temporary redirect (",[130,262,263],{},"302",") that browsers do not cache. A redirect cannot carry the CORS header that an integrity check needs, so use ",[130,266,267],{},"latest"," without ",[130,270,249],{},", for a quick try, and pin a version with its hash in production.",[116,273,274],{},"To import it from your own module instead:",[147,276,280],{"className":277,"code":278,"language":279,"meta":152,"style":152},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { deviceToken } from \"https:\u002F\u002Fopenshield.dev\u002Fsdk\u002Fdevice\u002Fv0.10.0\u002Fdevice.js\"\n","js",[130,281,282],{"__ignoreMap":152},[156,283,284,288,291,295,298,301,304,306],{"class":158,"line":159},[156,285,287],{"class":286},"s7zQu","import",[156,289,290],{"class":162}," {",[156,292,294],{"class":293},"sTEyZ"," deviceToken",[156,296,297],{"class":162}," }",[156,299,300],{"class":286}," from",[156,302,303],{"class":162}," \"",[156,305,200],{"class":183},[156,307,187],{"class":162},[116,309,310,311,314,315,318,319,322],{},"The script keeps a random 128-bit id in your site's ",[130,312,313],{},"localStorage",", under the key ",[130,316,317],{},"openfraud_device",". It reads nothing from the canvas, the fonts, the audio or the hardware. Storing the id is subject to the ePrivacy rules in the EU (Article 5(3)): decide whether it is strictly necessary for security, or ask for consent. ",[130,320,321],{},"forgetDevice()"," removes it when a user withdraws consent.",[135,324,326],{"id":325},"send-a-token-with-each-sensitive-event","Send a token with each sensitive event",[116,328,329,330,333,334,337],{},"Call ",[130,331,332],{},"deviceToken()"," just before each sensitive request: a login, a password change, a payment. It returns a new token every time, made of the device id, a random nonce, the time, the time zone, the platform, touch support and the automation flag. Send it to your server with the form, and your server puts it in the event as ",[130,335,336],{},"device_token",":",[147,339,341],{"className":277,"code":340,"language":279,"meta":152,"style":152},"\u002F\u002F In the browser\nimport { deviceToken } from \"https:\u002F\u002Fopenshield.dev\u002Fsdk\u002Fdevice\u002Fv0.10.0\u002Fdevice.js\"\n\nawait fetch(\"\u002Flogin\", {\n  method: \"POST\",\n  headers: { \"content-type\": \"application\u002Fjson\" },\n  body: JSON.stringify({ email, password, device_token: deviceToken() }),\n})\n",[130,342,343,349,367,373,398,415,443,493],{"__ignoreMap":152},[156,344,345],{"class":158,"line":159},[156,346,348],{"class":347},"sHwdD","\u002F\u002F In the browser\n",[156,350,351,353,355,357,359,361,363,365],{"class":158,"line":170},[156,352,287],{"class":286},[156,354,290],{"class":162},[156,356,294],{"class":293},[156,358,297],{"class":162},[156,360,300],{"class":286},[156,362,303],{"class":162},[156,364,200],{"class":183},[156,366,187],{"class":162},[156,368,369],{"class":158,"line":190},[156,370,372],{"emptyLinePlaceholder":371},true,"\n",[156,374,375,378,382,385,387,390,392,395],{"class":158,"line":205},[156,376,377],{"class":286},"await",[156,379,381],{"class":380},"s2Zo4"," fetch",[156,383,384],{"class":293},"(",[156,386,180],{"class":162},[156,388,389],{"class":183},"\u002Flogin",[156,391,180],{"class":162},[156,393,394],{"class":162},",",[156,396,397],{"class":162}," {\n",[156,399,400,403,405,407,410,412],{"class":158,"line":220},[156,401,402],{"class":166},"  method",[156,404,337],{"class":162},[156,406,303],{"class":162},[156,408,409],{"class":183},"POST",[156,411,180],{"class":162},[156,413,414],{"class":162},",\n",[156,416,417,420,422,424,426,429,431,433,435,438,440],{"class":158,"line":235},[156,418,419],{"class":166},"  headers",[156,421,337],{"class":162},[156,423,290],{"class":162},[156,425,303],{"class":162},[156,427,428],{"class":166},"content-type",[156,430,180],{"class":162},[156,432,337],{"class":162},[156,434,303],{"class":162},[156,436,437],{"class":183},"application\u002Fjson",[156,439,180],{"class":162},[156,441,442],{"class":162}," },\n",[156,444,446,449,451,454,457,460,462,465,468,470,473,475,478,480,482,485,488,491],{"class":158,"line":445},7,[156,447,448],{"class":166},"  body",[156,450,337],{"class":162},[156,452,453],{"class":293}," JSON",[156,455,456],{"class":162},".",[156,458,459],{"class":380},"stringify",[156,461,384],{"class":293},[156,463,464],{"class":162},"{",[156,466,467],{"class":293}," email",[156,469,394],{"class":162},[156,471,472],{"class":293}," password",[156,474,394],{"class":162},[156,476,477],{"class":166}," device_token",[156,479,337],{"class":162},[156,481,294],{"class":380},[156,483,484],{"class":293},"() ",[156,486,487],{"class":162},"}",[156,489,490],{"class":293},")",[156,492,414],{"class":162},[156,494,496,498],{"class":158,"line":495},8,[156,497,487],{"class":162},[156,499,500],{"class":293},")\n",[147,502,504],{"className":277,"code":503,"language":279,"meta":152,"style":152},"\u002F\u002F On your server, with the end user's context (see \"Send the end user's context\")\nconst decision = await fraud.decide({\n  type: \"session.login\",\n  actor_id: user.id,\n  email,\n  ip: endUserIp,\n  user_agent: endUserAgent,\n  country: endUserCountry,\n  device_token: body.device_token,\n})\n",[130,505,506,511,537,552,569,576,588,600,612,629],{"__ignoreMap":152},[156,507,508],{"class":158,"line":159},[156,509,510],{"class":347},"\u002F\u002F On your server, with the end user's context (see \"Send the end user's context\")\n",[156,512,513,516,519,521,524,527,529,532,534],{"class":158,"line":170},[156,514,515],{"class":173},"const",[156,517,518],{"class":293}," decision ",[156,520,177],{"class":162},[156,522,523],{"class":286}," await",[156,525,526],{"class":293}," fraud",[156,528,456],{"class":162},[156,530,531],{"class":380},"decide",[156,533,384],{"class":293},[156,535,536],{"class":162},"{\n",[156,538,539,541,543,545,548,550],{"class":158,"line":190},[156,540,174],{"class":166},[156,542,337],{"class":162},[156,544,303],{"class":162},[156,546,547],{"class":183},"session.login",[156,549,180],{"class":162},[156,551,414],{"class":162},[156,553,554,557,559,562,564,567],{"class":158,"line":205},[156,555,556],{"class":166},"  actor_id",[156,558,337],{"class":162},[156,560,561],{"class":293}," user",[156,563,456],{"class":162},[156,565,566],{"class":293},"id",[156,568,414],{"class":162},[156,570,571,574],{"class":158,"line":220},[156,572,573],{"class":293},"  email",[156,575,414],{"class":162},[156,577,578,581,583,586],{"class":158,"line":235},[156,579,580],{"class":166},"  ip",[156,582,337],{"class":162},[156,584,585],{"class":293}," endUserIp",[156,587,414],{"class":162},[156,589,590,593,595,598],{"class":158,"line":445},[156,591,592],{"class":166},"  user_agent",[156,594,337],{"class":162},[156,596,597],{"class":293}," endUserAgent",[156,599,414],{"class":162},[156,601,602,605,607,610],{"class":158,"line":495},[156,603,604],{"class":166},"  country",[156,606,337],{"class":162},[156,608,609],{"class":293}," endUserCountry",[156,611,414],{"class":162},[156,613,615,618,620,623,625,627],{"class":158,"line":614},9,[156,616,617],{"class":166},"  device_token",[156,619,337],{"class":162},[156,621,622],{"class":293}," body",[156,624,456],{"class":162},[156,626,336],{"class":293},[156,628,414],{"class":162},[156,630,632,634],{"class":158,"line":631},10,[156,633,487],{"class":162},[156,635,500],{"class":293},[116,637,638],{},"Make a new token for each submit. A token is single use: sending the same one twice for the same event type is a replay.",[135,640,642],{"id":641},"what-your-instance-reads","What your instance reads",[116,644,645,646,337],{},"Your instance checks the token against the request and keeps the result in the event, as ",[130,647,648],{},"event.device_token",[650,651,652,668],"table",{},[653,654,655],"thead",{},[656,657,658,662,665],"tr",{},[659,660,661],"th",{},"Value",[659,663,664],{},"Meaning",[659,666,667],{},"Signal",[669,670,671,685,700,715,729],"tbody",{},[656,672,673,679,682],{},[674,675,676],"td",{},[130,677,678],{},"ok",[674,680,681],{},"recent, not used before for this event type, and readable",[674,683,684],{},"none",[656,686,687,692,695],{},[674,688,689],{},[130,690,691],{},"stale",[674,693,694],{},"older than 120 seconds",[674,696,697],{},[130,698,699],{},"invalid_device_data",[656,701,702,707,710],{},[674,703,704],{},[130,705,706],{},"replayed",[674,708,709],{},"its nonce was used before for the same event type",[674,711,712],{},[130,713,714],{},"replayed_device_data",[656,716,717,722,725],{},[674,718,719],{},[130,720,721],{},"invalid",[674,723,724],{},"it cannot be read",[674,726,727],{},[130,728,699],{},[656,730,731,736,739],{},[674,732,733],{},[130,734,735],{},"missing",[674,737,738],{},"no token, and the setting below is on",[674,740,741],{},[130,742,743],{},"missing_device_data",[116,745,746,747,750,751,754,755,758,759,754,762,754,765,754,768,754,771,754,774,754,777,780,781,784,785,788,789,792,793,758,796,799,800,758,803,806,807,809,810,813,814,817],{},"From a readable token it fills ",[130,748,749],{},"event.device_info",": ",[130,752,753],{},"timezone",", ",[130,756,757],{},"platform"," (",[130,760,761],{},"windows",[130,763,764],{},"macos",[130,766,767],{},"ios",[130,769,770],{},"android",[130,772,773],{},"linux",[130,775,776],{},"linux_arm",[130,778,779],{},"chromeos"," or ",[130,782,783],{},"other","), ",[130,786,787],{},"touch"," and ",[130,790,791],{},"webdriver",". It compares the platform with your ",[130,794,795],{},"user_agent",[130,797,798],{},"spoofed_device",") and the time zone with the ",[130,801,802],{},"country",[130,804,805],{},"timezone_area_mismatch","), and a ",[130,808,791],{}," flag marks an automated browser (",[130,811,812],{},"headless_browser","). The device id in the token becomes the event's ",[130,815,816],{},"device"," when the event has none. The token itself is not stored.",[116,819,820],{},"The script holds no secret, so a token proves freshness and single use, not authenticity: a determined attacker can forge one. It stops the replay of a captured request and catches automated browsers that do not hide.",[135,822,824,825],{"id":823},"turn-on-require_token","Turn on ",[130,826,827],{},"require_token",[116,829,830,831,834,835,838,839,841,842,844,845,848],{},"Once every page that sends a sensitive event sends a token, turn on ",[125,832,833],{},"Require the device token"," in the dashboard, under ",[125,836,837],{},"Fraud settings",". From then on an event without a token counts as ",[130,840,735],{}," and raises ",[130,843,743],{},". Turn it on before every page sends one and your own legitimate events are flagged. The signals show in the dashboard under ",[125,846,847],{},"Events",", in plain words with the raw name beside.",[135,850,852],{"id":851},"the-challenge-loop","The challenge loop",[116,854,855,856,858],{},"A ",[130,857,132],{}," decision means: ask for proof before you go on (MFA, a confirmation email, a CAPTCHA). Your instance learns from how the proof went, and a passed proof earns the device some trust.",[860,861,862,871,880],"ol",{},[863,864,865,868,869,456],"li",{},[125,866,867],{},"Decide."," A login answers ",[130,870,132],{},[863,872,873,876,877,456],{},[125,874,875],{},"Show your challenge"," and report that you did, with ",[130,878,879],{},"challenge.requested",[863,881,882,754,885,780,888,891],{},[125,883,884],{},"Report the outcome",[130,886,887],{},"challenge.succeeded",[130,889,890],{},"challenge.failed",", with the account and the device.",[147,893,895],{"className":277,"code":894,"language":279,"meta":152,"style":152},"const decision = await fraud.decide({ type: \"session.login\", actor_id: user.id, device_token, ip, user_agent, country })\n\nif (decision.decision === \"step_up\") {\n  await fraud.track({ type: \"challenge.requested\", actor_id: user.id, device: deviceId })\n  const passed = await askForProof(user) \u002F\u002F your own MFA, email code or CAPTCHA\n  await fraud.track({\n    type: passed ? \"challenge.succeeded\" : \"challenge.failed\",\n    actor_id: user.id,\n    device: deviceId,\n  })\n  if (!passed) return refuse()\n}\n",[130,896,897,964,968,995,1047,1073,1087,1116,1131,1142,1149,1174],{"__ignoreMap":152},[156,898,899,901,903,905,907,909,911,913,915,917,920,922,924,926,928,930,933,935,937,939,941,943,945,947,950,952,955,957,960,962],{"class":158,"line":159},[156,900,515],{"class":173},[156,902,518],{"class":293},[156,904,177],{"class":162},[156,906,523],{"class":286},[156,908,526],{"class":293},[156,910,456],{"class":162},[156,912,531],{"class":380},[156,914,384],{"class":293},[156,916,464],{"class":162},[156,918,919],{"class":166}," type",[156,921,337],{"class":162},[156,923,303],{"class":162},[156,925,547],{"class":183},[156,927,180],{"class":162},[156,929,394],{"class":162},[156,931,932],{"class":166}," actor_id",[156,934,337],{"class":162},[156,936,561],{"class":293},[156,938,456],{"class":162},[156,940,566],{"class":293},[156,942,394],{"class":162},[156,944,477],{"class":293},[156,946,394],{"class":162},[156,948,949],{"class":293}," ip",[156,951,394],{"class":162},[156,953,954],{"class":293}," user_agent",[156,956,394],{"class":162},[156,958,959],{"class":293}," country ",[156,961,487],{"class":162},[156,963,500],{"class":293},[156,965,966],{"class":158,"line":170},[156,967,372],{"emptyLinePlaceholder":371},[156,969,970,973,976,978,981,984,986,988,990,993],{"class":158,"line":190},[156,971,972],{"class":286},"if",[156,974,975],{"class":293}," (decision",[156,977,456],{"class":162},[156,979,980],{"class":293},"decision ",[156,982,983],{"class":162},"===",[156,985,303],{"class":162},[156,987,132],{"class":183},[156,989,180],{"class":162},[156,991,992],{"class":293},") ",[156,994,536],{"class":162},[156,996,997,1000,1002,1004,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027,1029,1031,1033,1035,1038,1040,1043,1045],{"class":158,"line":205},[156,998,999],{"class":286},"  await",[156,1001,526],{"class":293},[156,1003,456],{"class":162},[156,1005,1006],{"class":380},"track",[156,1008,384],{"class":166},[156,1010,464],{"class":162},[156,1012,919],{"class":166},[156,1014,337],{"class":162},[156,1016,303],{"class":162},[156,1018,879],{"class":183},[156,1020,180],{"class":162},[156,1022,394],{"class":162},[156,1024,932],{"class":166},[156,1026,337],{"class":162},[156,1028,561],{"class":293},[156,1030,456],{"class":162},[156,1032,566],{"class":293},[156,1034,394],{"class":162},[156,1036,1037],{"class":166}," device",[156,1039,337],{"class":162},[156,1041,1042],{"class":293}," deviceId",[156,1044,297],{"class":162},[156,1046,500],{"class":166},[156,1048,1049,1052,1055,1058,1060,1063,1065,1068,1070],{"class":158,"line":220},[156,1050,1051],{"class":173},"  const",[156,1053,1054],{"class":293}," passed",[156,1056,1057],{"class":162}," =",[156,1059,523],{"class":286},[156,1061,1062],{"class":380}," askForProof",[156,1064,384],{"class":166},[156,1066,1067],{"class":293},"user",[156,1069,992],{"class":166},[156,1071,1072],{"class":347},"\u002F\u002F your own MFA, email code or CAPTCHA\n",[156,1074,1075,1077,1079,1081,1083,1085],{"class":158,"line":235},[156,1076,999],{"class":286},[156,1078,526],{"class":293},[156,1080,456],{"class":162},[156,1082,1006],{"class":380},[156,1084,384],{"class":166},[156,1086,536],{"class":162},[156,1088,1089,1092,1094,1096,1099,1101,1103,1105,1108,1110,1112,1114],{"class":158,"line":445},[156,1090,1091],{"class":166},"    type",[156,1093,337],{"class":162},[156,1095,1054],{"class":293},[156,1097,1098],{"class":162}," ?",[156,1100,303],{"class":162},[156,1102,887],{"class":183},[156,1104,180],{"class":162},[156,1106,1107],{"class":162}," :",[156,1109,303],{"class":162},[156,1111,890],{"class":183},[156,1113,180],{"class":162},[156,1115,414],{"class":162},[156,1117,1118,1121,1123,1125,1127,1129],{"class":158,"line":495},[156,1119,1120],{"class":166},"    actor_id",[156,1122,337],{"class":162},[156,1124,561],{"class":293},[156,1126,456],{"class":162},[156,1128,566],{"class":293},[156,1130,414],{"class":162},[156,1132,1133,1136,1138,1140],{"class":158,"line":614},[156,1134,1135],{"class":166},"    device",[156,1137,337],{"class":162},[156,1139,1042],{"class":293},[156,1141,414],{"class":162},[156,1143,1144,1147],{"class":158,"line":631},[156,1145,1146],{"class":162},"  }",[156,1148,500],{"class":166},[156,1150,1152,1155,1157,1160,1163,1165,1168,1171],{"class":158,"line":1151},11,[156,1153,1154],{"class":286},"  if",[156,1156,758],{"class":166},[156,1158,1159],{"class":162},"!",[156,1161,1162],{"class":293},"passed",[156,1164,992],{"class":166},[156,1166,1167],{"class":286},"return",[156,1169,1170],{"class":380}," refuse",[156,1172,1173],{"class":166},"()\n",[156,1175,1177],{"class":158,"line":1176},12,[156,1178,1179],{"class":162},"}\n",[116,1181,1182,1185,1186,1189,1190,1192,1193,1195],{},[130,1183,1184],{},"deviceId"," is the id of the device (",[130,1187,1188],{},"dev_...","), which your instance read from the token of the login. Send ",[130,1191,816],{}," on ",[130,1194,887],{},": it is the device that becomes trusted.",[116,1197,1198,1199,1202,1203,1207],{},"Your server sends the outcome, after it checked the proof itself (the code matched, the MFA answer verified). Never pass on an outcome your page reports: a page can claim anything, so an event marked ",[130,1200,1201],{},"\"reported_by\": \"client\""," loses its account and trusts no device (see ",[120,1204,1206],{"href":1205},"\u002Fdocs\u002Ffraud-prevention#before-sign-in-and-events-a-page-reports","events a page reports",").",[116,1209,1210],{},"What the instance does with it:",[1212,1213,1214,1227,1240],"ul",{},[863,1215,1216,1219,1220,1222,1223,1226],{},[125,1217,1218],{},"A pass trusts the pair for 14 days."," ",[130,1221,887],{}," puts the account and the device on the ",[130,1224,1225],{},"trusted_devices"," list for 14 days. The next logins from that device are allowed without asking for proof again, and each new pass restarts the 14 days. A device is trusted for one account only: on another account it is a new device.",[863,1228,1229,1232,1233,1235,1236,1239],{},[125,1230,1231],{},"Three failures in an hour are a hold."," Three ",[130,1234,890],{}," on an account within an hour fire the rule ",[130,1237,1238],{},"ato_failed_challenges",", which holds the account's next sensitive actions until the owner confirms.",[863,1241,1242,1245,1246,1248,1249,1252,1253,750,1256,1259,1260,1263],{},[125,1243,1244],{},"A lost device is archived."," When the owner reports a lost device, archive the pair on ",[130,1247,1225],{}," with a ",[130,1250,1251],{},"fraud"," key, or in the dashboard under ",[125,1254,1255],{},"Lists",[130,1257,1258],{},"POST \u002Ffraud\u002Fv1\u002Flists\u002Ftrusted_devices\u002Fitems\u002Farchive"," with ",[130,1261,1262],{},"{\"items\": [{\"value\": \"u_123\", \"second\": \"dev_8f2c41\"}]}",", the values as the list shows them.",[116,1265,1266,1267,1271,1272,1274],{},"These rules start in shadow like every rule that is not a block: until they are promoted (see ",[120,1268,1270],{"href":1269},"\u002Fdocs\u002Ffraud-prevention#when-rules-start-to-act","when rules start to act","), the answers list what they would have done. ",[130,1273,879],{}," and the outcomes themselves are recorded from the first call.",[135,1276,1278],{"id":1277},"sandbox-mock-tokens","Sandbox mock tokens",[116,1280,1281,1282,1285,1286,1289,1290,456],{},"Coming soon. A sandbox instance will read a mock token such as ",[130,1283,1284],{},"test|device:d1|verdict:deny|score:80|ip:BE"," and answer from it, so your tests can drive every branch of this loop (allow, step up, block) without writing a rule. openshield does not offer a sandbox yet: a ",[130,1287,1288],{},"test|"," token sent to a regular instance is ",[130,1291,721],{},[1293,1294,1295],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":152,"searchDepth":170,"depth":170,"links":1297},[1298,1299,1300,1301,1303,1304],{"id":137,"depth":170,"text":138},{"id":325,"depth":170,"text":326},{"id":641,"depth":170,"text":642},{"id":823,"depth":170,"text":1302},"Turn on require_token",{"id":851,"depth":170,"text":852},{"id":1277,"depth":170,"text":1278},"Add the device script to your pages, send its token with each sensitive event, and report the outcome of a step-up challenge.","md",null,{},{"title":1310,"description":1311},"Device token and step-up challenges","Include the device script with an integrity hash, send device_token with each event, and report challenges so a passed proof trusts the device.","aGEwmQiRXI9exTfjnTuVctRopwG3INImEHXjfY9JZ24",[1314,1316],{"title":99,"path":100,"stem":101,"description":1315,"children":-1},"Score sign-ups, logins and payments with \u002Ffraud\u002Fv1\u002Fdecide, report events with \u002Ffraud\u002Fv1\u002Fevents, and get a webhook when a case opens.",{"title":107,"path":108,"stem":109,"description":1317,"children":-1},"New features and changes to the openshield API and dashboard, newest first.",1792523226730]