[{"data":1,"prerenderedAt":1247},["ShallowReactive",2],{"Categories":3,"NavIndexCategoriesCountFooter":203,"content-\u002F2020\u002F03\u002F08\u002Fbiometric-authentication\u002F":204},[4,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202],{"category":5},"System Administration",{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},"Software Development",{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":27},{"category":27},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":5},{"category":27},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":27},{"category":27},{"category":67},"Drones & RC",{"category":69},"DIY Projects",{"category":67},{"category":72},"Photography",{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":69},{"category":67},{"category":69},{"category":69},{"category":67},{"category":67},{"category":72},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":5},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":5},{"category":67},{"category":67},{"category":72},{"category":72},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":67},{"category":72},{"category":67},{"category":138},"3D Printing - Laser Cutting - CNC",{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":138},{"category":5},{"category":138},{"category":27},{"category":27},{"category":138},{"category":138},{"category":72},{"category":158},"Photography,3D Printing - Laser Cutting - CNC",{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":5},{"category":67},{"category":5},{"category":5},{"category":27},{"category":27},{"category":27},{"category":27},{"category":27},{"category":69},{"category":27},{"category":27},{"category":27},{"category":27},{"category":181},"Home Assistant",{"category":181},{"category":72},{"category":27},{"category":27},{"category":72},{"category":138},{"category":5},{"category":72},{"category":72},{"category":138},{"category":27},{"category":181},{"category":181},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},{"category":72},191,{"id":205,"title":206,"body":207,"category":1232,"date":1233,"description":213,"embedImage":1232,"extension":1234,"image":1232,"intro":1235,"meta":1236,"navigation":377,"path":1237,"seo":1238,"series":1239,"sitemap":1240,"stem":1241,"tags":1242,"__hash__":1246},"content\u002F2020\u002F03\u002F08\u002Fbiometric-authentication.md","Biometric authentication",{"type":208,"value":209,"toc":1218},"minimark",[210,214,217,220,225,228,231,303,306,308,312,315,318,329,636,641,655,659,662,665,673,675,679,682,706,709,818,821,824,827,835,839,846,937,941,947,1067,1071,1078,1178,1180,1184,1191,1193,1197,1200,1202,1214],[211,212,213],"p",{},"So far we have an ap that can scan its config from a QR code and persist it in the keychain on the device.",[211,215,216],{},"Today we'll add support for using biometric unlock (Face ID or Touch ID depending on the device).",[218,219],"hr",{},[221,222,224],"h2",{"id":223},"simulator-data","Simulator Data",[211,226,227],{},"However - first we'll make one super quick change to make testing in the simulator a little easier. We'll return a dummy config json in the config scanner.",[211,229,230],{},"In ScannerView - create a constant:",[232,233,238],"pre",{"className":234,"code":235,"language":236,"meta":237,"style":237},"language-swift shiki shiki-themes github-dark","let simulatedData = \"\"\"\n{\n    \"clientId\": \"clientId\",\n    \"clientSecret\": \"clientSecret\",\n    \"userId\": \"userId\",\n    \"accountNr\": \"12345678903\"\n}\n\"\"\"\n","swift","",[239,240,241,261,267,273,279,285,291,297],"code",{"__ignoreMap":237},[242,243,246,250,254,257],"span",{"class":244,"line":245},"line",1,[242,247,249],{"class":248},"snl16","let",[242,251,253],{"class":252},"s95oV"," simulatedData ",[242,255,256],{"class":248},"=",[242,258,260],{"class":259},"sU2Wk"," \"\"\"\n",[242,262,264],{"class":244,"line":263},2,[242,265,266],{"class":259},"{\n",[242,268,270],{"class":244,"line":269},3,[242,271,272],{"class":259},"    \"clientId\": \"clientId\",\n",[242,274,276],{"class":244,"line":275},4,[242,277,278],{"class":259},"    \"clientSecret\": \"clientSecret\",\n",[242,280,282],{"class":244,"line":281},5,[242,283,284],{"class":259},"    \"userId\": \"userId\",\n",[242,286,288],{"class":244,"line":287},6,[242,289,290],{"class":259},"    \"accountNr\": \"12345678903\"\n",[242,292,294],{"class":244,"line":293},7,[242,295,296],{"class":259},"}\n",[242,298,300],{"class":244,"line":299},8,[242,301,302],{"class":259},"\"\"\"\n",[211,304,305],{},"And then in the call to CodeScannerView pass the simulatedData variable instead of \"-\"",[218,307],{},[221,309,311],{"id":310},"touch-idface-id","Touch ID\u002FFace ID",[211,313,314],{},"These use Apple's LocalAuthentication module.",[211,316,317],{},"Create a swift file to hold some utility functions - Authentication.swift.",[211,319,320,321,324,325,328],{},"Import ",[239,322,323],{},"LocalAuthentication"," just after ",[239,326,327],{},"Foundation"," then create the following utility method and enum:",[232,330,332],{"className":234,"code":331,"language":236,"meta":237,"style":237},"enum AuthStatus {\n    case OK\n    case Error\n    case Unavailable\n}\n\nfunc authenticateUser(_ callback: @escaping (_ status: AuthStatus) -> Void) {\n    let context = LAContext()\n    var error: NSError?\n\n    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {\n        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: \"Unlock\") { success, authenticationError in\n            DispatchQueue.main.async {\n                if success {\n                    print(\"Authentication OK\")\n                    callback(.OK)\n                } else {\n                    print(\"Authentication Error\")\n                    callback(.Error)\n                }\n            }\n        }\n    } else {\n        print(\"Authentication Unavailable\")\n        callback(.Unavailable)\n    }\n}\n",[239,333,334,346,355,362,369,373,379,411,427,439,444,471,496,507,516,530,539,550,562,575,581,587,593,603,616,625,631],{"__ignoreMap":237},[242,335,336,339,343],{"class":244,"line":245},[242,337,338],{"class":248},"enum",[242,340,342],{"class":341},"svObZ"," AuthStatus",[242,344,345],{"class":252}," {\n",[242,347,348,351],{"class":244,"line":263},[242,349,350],{"class":248},"    case",[242,352,354],{"class":353},"sDLfK"," OK\n",[242,356,357,359],{"class":244,"line":269},[242,358,350],{"class":248},[242,360,361],{"class":353}," Error\n",[242,363,364,366],{"class":244,"line":275},[242,365,350],{"class":248},[242,367,368],{"class":353}," Unavailable\n",[242,370,371],{"class":244,"line":281},[242,372,296],{"class":252},[242,374,375],{"class":244,"line":287},[242,376,378],{"emptyLinePlaceholder":377},true,"\n",[242,380,381,384,387,390,393,396,399,402,405,408],{"class":244,"line":293},[242,382,383],{"class":248},"func",[242,385,386],{"class":341}," authenticateUser",[242,388,389],{"class":252},"(",[242,391,392],{"class":341},"_",[242,394,395],{"class":252}," callback: ",[242,397,398],{"class":248},"@escaping",[242,400,401],{"class":252}," (_ status: AuthStatus) ",[242,403,404],{"class":248},"->",[242,406,407],{"class":353}," Void",[242,409,410],{"class":252},") {\n",[242,412,413,416,419,421,424],{"class":244,"line":299},[242,414,415],{"class":248},"    let",[242,417,418],{"class":252}," context ",[242,420,256],{"class":248},[242,422,423],{"class":353}," LAContext",[242,425,426],{"class":252},"()\n",[242,428,430,433,436],{"class":244,"line":429},9,[242,431,432],{"class":248},"    var",[242,434,435],{"class":252}," error: NSError",[242,437,438],{"class":248},"?\n",[242,440,442],{"class":244,"line":441},10,[242,443,378],{"emptyLinePlaceholder":377},[242,445,447,450,453,456,459,462,465,468],{"class":244,"line":446},11,[242,448,449],{"class":248},"    if",[242,451,452],{"class":252}," context.",[242,454,455],{"class":353},"canEvaluatePolicy",[242,457,458],{"class":252},"(.deviceOwnerAuthenticationWithBiometrics, ",[242,460,461],{"class":353},"error",[242,463,464],{"class":252},": ",[242,466,467],{"class":248},"&",[242,469,470],{"class":252},"error) {\n",[242,472,474,477,480,482,485,487,490,493],{"class":244,"line":473},12,[242,475,476],{"class":252},"        context.",[242,478,479],{"class":353},"evaluatePolicy",[242,481,458],{"class":252},[242,483,484],{"class":353},"localizedReason",[242,486,464],{"class":252},[242,488,489],{"class":259},"\"Unlock\"",[242,491,492],{"class":252},") { success, authenticationError ",[242,494,495],{"class":248},"in\n",[242,497,499,502,505],{"class":244,"line":498},13,[242,500,501],{"class":252},"            DispatchQueue.main.",[242,503,504],{"class":353},"async",[242,506,345],{"class":252},[242,508,510,513],{"class":244,"line":509},14,[242,511,512],{"class":248},"                if",[242,514,515],{"class":252}," success {\n",[242,517,519,522,524,527],{"class":244,"line":518},15,[242,520,521],{"class":353},"                    print",[242,523,389],{"class":252},[242,525,526],{"class":259},"\"Authentication OK\"",[242,528,529],{"class":252},")\n",[242,531,533,536],{"class":244,"line":532},16,[242,534,535],{"class":353},"                    callback",[242,537,538],{"class":252},"(.OK)\n",[242,540,542,545,548],{"class":244,"line":541},17,[242,543,544],{"class":252},"                } ",[242,546,547],{"class":248},"else",[242,549,345],{"class":252},[242,551,553,555,557,560],{"class":244,"line":552},18,[242,554,521],{"class":353},[242,556,389],{"class":252},[242,558,559],{"class":259},"\"Authentication Error\"",[242,561,529],{"class":252},[242,563,565,567,570,573],{"class":244,"line":564},19,[242,566,535],{"class":353},[242,568,569],{"class":252},"(.",[242,571,572],{"class":353},"Error",[242,574,529],{"class":252},[242,576,578],{"class":244,"line":577},20,[242,579,580],{"class":252},"                }\n",[242,582,584],{"class":244,"line":583},21,[242,585,586],{"class":252},"            }\n",[242,588,590],{"class":244,"line":589},22,[242,591,592],{"class":252},"        }\n",[242,594,596,599,601],{"class":244,"line":595},23,[242,597,598],{"class":252},"    } ",[242,600,547],{"class":248},[242,602,345],{"class":252},[242,604,606,609,611,614],{"class":244,"line":605},24,[242,607,608],{"class":353},"        print",[242,610,389],{"class":252},[242,612,613],{"class":259},"\"Authentication Unavailable\"",[242,615,529],{"class":252},[242,617,619,622],{"class":244,"line":618},25,[242,620,621],{"class":353},"        callback",[242,623,624],{"class":252},"(.Unavailable)\n",[242,626,628],{"class":244,"line":627},26,[242,629,630],{"class":252},"    }\n",[242,632,634],{"class":244,"line":633},27,[242,635,296],{"class":252},[637,638,640],"h3",{"id":639},"notes","Notes",[642,643,644,652],"ul",{},[645,646,647,648,651],"li",{},"We're calling an older Objective-C api here - things like ",[239,649,650],{},"error: &error"," don't really feel so \"swift\" but they still work.",[645,653,654],{},"The call to evaluatePolicy happens in a different thread - we need to switch back to our main thread to handle the result",[637,656,658],{"id":657},"face-id","Face ID",[211,660,661],{},"But wait - this will only work for Touch ID. We gave a (too brief) reason to the call to evaluatePolicy here - but that is only used for Touch ID. For Face ID we have to add it to the Info.plist instead.",[211,663,664],{},"Open Info.plist and add a new line:",[232,666,671],{"className":667,"code":669,"language":670},[668],"language-text","\"Privacy - Face ID Usage Description\" - \"Unlock\"\n","text",[239,672,669],{"__ignoreMap":237},[218,674],{},[221,676,678],{"id":677},"using-the-authenticate-function-in-the-view","Using the authenticate function in the view",[211,680,681],{},"Add the following state variable:",[232,683,685],{"className":234,"code":684,"language":236,"meta":237,"style":237},"@State private var authenticated = false\n",[239,686,687],{"__ignoreMap":237},[242,688,689,692,695,698,701,703],{"class":244,"line":245},[242,690,691],{"class":248},"@State",[242,693,694],{"class":248}," private",[242,696,697],{"class":248}," var",[242,699,700],{"class":252}," authenticated ",[242,702,256],{"class":248},[242,704,705],{"class":353}," false\n",[211,707,708],{},"A quick utility function to call and handle the response:",[232,710,712],{"className":234,"code":711,"language":236,"meta":237,"style":237},"func askForAuth() {\n    authenticateUser() { status in\n        switch(status) {\n        case .OK:\n            self.authenticated = true\n        case .Error:\n            self.authenticated = false\n        case .Unavailable:\n            self.authenticated = true\n        }\n    }\n}\n",[239,713,714,724,734,742,753,766,777,787,796,806,810,814],{"__ignoreMap":237},[242,715,716,718,721],{"class":244,"line":245},[242,717,383],{"class":248},[242,719,720],{"class":341}," askForAuth",[242,722,723],{"class":252},"() {\n",[242,725,726,729,732],{"class":244,"line":263},[242,727,728],{"class":353},"    authenticateUser",[242,730,731],{"class":252},"() { status ",[242,733,495],{"class":248},[242,735,736,739],{"class":244,"line":269},[242,737,738],{"class":248},"        switch",[242,740,741],{"class":252},"(status) {\n",[242,743,744,747,750],{"class":244,"line":275},[242,745,746],{"class":248},"        case",[242,748,749],{"class":252}," .OK",[242,751,752],{"class":248},":\n",[242,754,755,758,761,763],{"class":244,"line":281},[242,756,757],{"class":353},"            self",[242,759,760],{"class":252},".authenticated ",[242,762,256],{"class":248},[242,764,765],{"class":353}," true\n",[242,767,768,770,773,775],{"class":244,"line":287},[242,769,746],{"class":248},[242,771,772],{"class":252}," .",[242,774,572],{"class":353},[242,776,752],{"class":248},[242,778,779,781,783,785],{"class":244,"line":293},[242,780,757],{"class":353},[242,782,760],{"class":252},[242,784,256],{"class":248},[242,786,705],{"class":353},[242,788,789,791,794],{"class":244,"line":299},[242,790,746],{"class":248},[242,792,793],{"class":252}," .Unavailable",[242,795,752],{"class":248},[242,797,798,800,802,804],{"class":244,"line":429},[242,799,757],{"class":353},[242,801,760],{"class":252},[242,803,256],{"class":248},[242,805,765],{"class":353},[242,807,808],{"class":244,"line":441},[242,809,592],{"class":252},[242,811,812],{"class":244,"line":446},[242,813,630],{"class":252},[242,815,816],{"class":244,"line":473},[242,817,296],{"class":252},[211,819,820],{},"Note that if authentication is unavailable we're just letting the user in. Here you would need fallback to a login or similar - but for this proof of concept app (and given that my son has a touch ID device) this will do here.",[211,822,823],{},"So - when do we call this?",[211,825,826],{},"I want to call this in two places:",[642,828,829,832],{},[645,830,831],{},"When the app starts - if we already have configuration",[645,833,834],{},"When a new config successfully is scanned",[637,836,838],{"id":837},"app-load","App Load",[211,840,841,842,845],{},"Add the if clause after the call to ",[239,843,844],{},"loadConfig()",":",[232,847,849],{"className":234,"code":848,"language":236,"meta":237,"style":237},".onAppear {\n    self.config = Config.loadConfig()\n\n    if (self.config != nil && self.authenticated == false) {\n        self.askForAuth()\n    }\n}\n",[239,850,851,861,879,883,917,929,933],{"__ignoreMap":237},[242,852,853,856,859],{"class":244,"line":245},[242,854,855],{"class":252},".",[242,857,858],{"class":353},"onAppear",[242,860,345],{"class":252},[242,862,863,866,869,871,874,877],{"class":244,"line":263},[242,864,865],{"class":353},"    self",[242,867,868],{"class":252},".config ",[242,870,256],{"class":248},[242,872,873],{"class":252}," Config.",[242,875,876],{"class":353},"loadConfig",[242,878,426],{"class":252},[242,880,881],{"class":244,"line":269},[242,882,378],{"emptyLinePlaceholder":377},[242,884,885,887,890,893,895,898,901,904,907,909,912,915],{"class":244,"line":275},[242,886,449],{"class":248},[242,888,889],{"class":252}," (",[242,891,892],{"class":353},"self",[242,894,868],{"class":252},[242,896,897],{"class":248},"!=",[242,899,900],{"class":353}," nil",[242,902,903],{"class":248}," &&",[242,905,906],{"class":353}," self",[242,908,760],{"class":252},[242,910,911],{"class":248},"==",[242,913,914],{"class":353}," false",[242,916,410],{"class":252},[242,918,919,922,924,927],{"class":244,"line":281},[242,920,921],{"class":353},"        self",[242,923,855],{"class":252},[242,925,926],{"class":353},"askForAuth",[242,928,426],{"class":252},[242,930,931],{"class":244,"line":287},[242,932,630],{"class":252},[242,934,935],{"class":244,"line":293},[242,936,296],{"class":252},[637,938,940],{"id":939},"config-load","Config load",[211,942,943,944,845],{},"In newScanData - add the if clause after the line ",[239,945,946],{},"self.config = config",[232,948,950],{"className":234,"code":949,"language":236,"meta":237,"style":237}," func newScanData(_ code: String) {\n     if let config = Config.decodeConfig(json: code) {\n         config.save()\n         self.config = config\n\n         if (self.authenticated == false) {\n             self.askForAuth()\n         }\n     }\n }\n",[239,951,952,972,998,1008,1020,1024,1041,1052,1057,1062],{"__ignoreMap":237},[242,953,954,957,960,962,964,967,970],{"class":244,"line":245},[242,955,956],{"class":248}," func",[242,958,959],{"class":341}," newScanData",[242,961,389],{"class":252},[242,963,392],{"class":341},[242,965,966],{"class":252}," code: ",[242,968,969],{"class":353},"String",[242,971,410],{"class":252},[242,973,974,977,980,983,985,987,990,992,995],{"class":244,"line":263},[242,975,976],{"class":248},"     if",[242,978,979],{"class":248}," let",[242,981,982],{"class":252}," config ",[242,984,256],{"class":248},[242,986,873],{"class":252},[242,988,989],{"class":353},"decodeConfig",[242,991,389],{"class":252},[242,993,994],{"class":353},"json",[242,996,997],{"class":252},": code) {\n",[242,999,1000,1003,1006],{"class":244,"line":269},[242,1001,1002],{"class":252},"         config.",[242,1004,1005],{"class":353},"save",[242,1007,426],{"class":252},[242,1009,1010,1013,1015,1017],{"class":244,"line":275},[242,1011,1012],{"class":353},"         self",[242,1014,868],{"class":252},[242,1016,256],{"class":248},[242,1018,1019],{"class":252}," config\n",[242,1021,1022],{"class":244,"line":281},[242,1023,378],{"emptyLinePlaceholder":377},[242,1025,1026,1029,1031,1033,1035,1037,1039],{"class":244,"line":287},[242,1027,1028],{"class":248},"         if",[242,1030,889],{"class":252},[242,1032,892],{"class":353},[242,1034,760],{"class":252},[242,1036,911],{"class":248},[242,1038,914],{"class":353},[242,1040,410],{"class":252},[242,1042,1043,1046,1048,1050],{"class":244,"line":293},[242,1044,1045],{"class":353},"             self",[242,1047,855],{"class":252},[242,1049,926],{"class":353},[242,1051,426],{"class":252},[242,1053,1054],{"class":244,"line":299},[242,1055,1056],{"class":252},"         }\n",[242,1058,1059],{"class":244,"line":429},[242,1060,1061],{"class":252},"     }\n",[242,1063,1064],{"class":244,"line":441},[242,1065,1066],{"class":252}," }\n",[637,1068,1070],{"id":1069},"view","View",[211,1072,1073,1074,1077],{},"In the view - we currently have a conditional display of the account number or the string \"You need to scan in a configuation\". Extend this to check the ",[239,1075,1076],{},"authenticated"," state:",[232,1079,1081],{"className":234,"code":1080,"language":236,"meta":237,"style":237},"if (self.config != nil) {\n    if (self.authenticated == false) {\n        Text(\"Please authenticate\")\n    } else {\n        Text(config!.accountNr)\n    }\n} else {\n    Text(\"You need to scan in a configuation\")\n}\n",[239,1082,1083,1100,1116,1128,1136,1149,1153,1162,1174],{"__ignoreMap":237},[242,1084,1085,1088,1090,1092,1094,1096,1098],{"class":244,"line":245},[242,1086,1087],{"class":248},"if",[242,1089,889],{"class":252},[242,1091,892],{"class":353},[242,1093,868],{"class":252},[242,1095,897],{"class":248},[242,1097,900],{"class":353},[242,1099,410],{"class":252},[242,1101,1102,1104,1106,1108,1110,1112,1114],{"class":244,"line":263},[242,1103,449],{"class":248},[242,1105,889],{"class":252},[242,1107,892],{"class":353},[242,1109,760],{"class":252},[242,1111,911],{"class":248},[242,1113,914],{"class":353},[242,1115,410],{"class":252},[242,1117,1118,1121,1123,1126],{"class":244,"line":269},[242,1119,1120],{"class":353},"        Text",[242,1122,389],{"class":252},[242,1124,1125],{"class":259},"\"Please authenticate\"",[242,1127,529],{"class":252},[242,1129,1130,1132,1134],{"class":244,"line":275},[242,1131,598],{"class":252},[242,1133,547],{"class":248},[242,1135,345],{"class":252},[242,1137,1138,1140,1143,1146],{"class":244,"line":281},[242,1139,1120],{"class":353},[242,1141,1142],{"class":252},"(config",[242,1144,1145],{"class":248},"!",[242,1147,1148],{"class":252},".accountNr)\n",[242,1150,1151],{"class":244,"line":287},[242,1152,630],{"class":252},[242,1154,1155,1158,1160],{"class":244,"line":293},[242,1156,1157],{"class":252},"} ",[242,1159,547],{"class":248},[242,1161,345],{"class":252},[242,1163,1164,1167,1169,1172],{"class":244,"line":299},[242,1165,1166],{"class":353},"    Text",[242,1168,389],{"class":252},[242,1170,1171],{"class":259},"\"You need to scan in a configuation\"",[242,1173,529],{"class":252},[242,1175,1176],{"class":244,"line":429},[242,1177,296],{"class":252},[218,1179],{},[221,1181,1183],{"id":1182},"simulator","Simulator",[211,1185,1186,1187,1190],{},"You can test both Face ID and Touch ID in the simulator if you use the menu ",[239,1188,1189],{},"Hardware > Face\u002FTouch ID"," first to enrol the device and then also to tell the simulator that the face\u002Ffingerprint is good.",[218,1192],{},[221,1194,1196],{"id":1195},"summary","Summary",[211,1198,1199],{},"We've now added basic support for biometric authentication. In the next post we'll actually start calling the S'banken API and then in further posts we will start doing something with the view layout.",[218,1201],{},[211,1203,1204],{},[1205,1206,1213],"a",{"href":1207,"rel":1208,"target":1212},"https:\u002F\u002Fgithub.com\u002Fchrissearle\u002Flommepenger-swiftui",[1209,1210,1211],"nofollow","noopener","noreferer","_blank","GitHub Repository",[1215,1216,1217],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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 pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":237,"searchDepth":263,"depth":263,"links":1219},[1220,1221,1225,1230,1231],{"id":223,"depth":263,"text":224},{"id":310,"depth":263,"text":311,"children":1222},[1223,1224],{"id":639,"depth":269,"text":640},{"id":657,"depth":269,"text":658},{"id":677,"depth":263,"text":678,"children":1226},[1227,1228,1229],{"id":837,"depth":269,"text":838},{"id":939,"depth":269,"text":940},{"id":1069,"depth":269,"text":1070},{"id":1182,"depth":263,"text":1183},{"id":1195,"depth":263,"text":1196},null,"2020-03-08 21:00 +0100","md","So far we have an ap that can scan its config from a QR code and persist it in the keychain on the device. Today we'll add support for using biometric unlock (Face ID or Touch ID depending on the device).",{},"\u002F2020\u002F03\u002F08\u002Fbiometric-authentication",{"title":206,"description":213},"Revisiting the Sbanken API with SwiftUI",{"loc":1237},"2020\u002F03\u002F08\u002Fbiometric-authentication",[1243,236,1244,1245],"ios","swiftui","xcode","voq9Cyw1UEN1h-nrf3IoCCXlc_A5zmvAY_lETV6GhKc",1775293006991]