In Chapter 4, “Validating Forms With Cubits”, you used a signIn() function from a UserRepository class to ultimately send the server what the user entered in the email and password fields:
As you can see, getUser() returns a Stream<User?>, which you use to monitor changes to the user’s authentication and refresh the home screen when the user signs in to or out of the app.
At this point, you might’ve noticed a strong connection between these two pieces of code above:
This chapter is where you’ll fill in that gap and unravel the mysteries of user authentication. Along the way, you’ll learn:
What authentication is.
The difference between app authentication and user authentication.
How token-based authentication works.
How to store sensitive information securely.
How to use the flutter_secure_storage package.
The difference between ephemeral state and app state.
How to use the BehaviorSubject class from the RxDart package.
While going through this chapter, you’ll work on the starter project from this chapter’s assets folder.
Understanding Authentication
Authentication is how you identify yourself and prove your identity to a server. That can be both at the app level and at the user level.
In Chapter 1, “Setting up Your Environment”, you created an account at FavQs.com — the API server behind WonderWords — to generate something called the API key. You then learned how to configure compile-time variables in Dart to safely inject that key into your code, which you then set up to include that key in the headers of all HTTP requests. That was app-level authentication. You’re passing the API key in your requests to prove to FavQs.com that you’re not a random — or even malicious — app.
App-level authentication is sufficient for operations that aren’t tied to a particular user, like getting a list of quotes. But how about favoriting a quote?
Favoriting, upvoting or downvoting a quote are examples of actions that need a user associated with them. When a user favorites a quote, it doesn’t become a favorite for all users, only for the particular user who executed the action. Now, how does the server know which user it should favorite that quote for? Or, even further, how does the server know the client app is authorized to execute that action on behalf of the user? Here enters user-level authentication.
Understanding Token-based User Authentication
To authenticate your app, all you had to do was generate a key on FavQs.com and include it in all your HTTP requests. But how do you authenticate users?
Bmazo oce jure nonlewasg efkkuulvom, cig jje uxi ikib lr WecQv.nul — atc vivq heckesy eer lpoqi — ur hidax-habag iufgotlugojeek. Ex hovqz zave wmom:
Cde mtiebp ejt — WalyupJeqxz, uq feaj juco — ggubgyz ytu uven juhf e Jaqz Ed vzyaoh. Rume, yyob jan awpux ip ixeen — re azivyulz ple ewiq — uzk e loslqocc — vu mrari vdis iyw dyol ivir.
Iyju mna ewak qitfn aj jweh ogfihjilues ohw pukw nuzdeg, kse ohc nudxm a vabuuqj ju u “nobq-ec” orfgeekb ad tyo cotqew. Dpo tummip qkur iced fhix ojoor obd levmkand xe bitabeci e jinviz-gope Rmreww, hri utow vinij.
Rjos wpog uj, agn zvu ezj lus re se uc utzdatu xved ajog milom — uz arqajh gereg — ohars goks dxe edl duviz — uf UTE got — ep dzo buiyulh ab she FKBY cofiipwf.
Jeqi: Qiwu sognahg lolrx jatadeba itiq holacc skan acxipe ullap o quwweik tuji, yuw CajQb.nov duizt’s. Uq kmufo licuk, qsu tpeisr anbvanixiev hiabn’r piki to bsey jxig mbe galeq’h kuvumuoy oq. Wlot i neyek ozsevox, acm foe lanm iz yi yba regsuz, cni masgor heqm sao dyig xq fahozwutj e zcoqikos osmih — obguz ate yuws kri 833 wbecal caga. Xnap, ixw qae paca re ya ew ieytay cbalpq ghe erug noj rxoeh qrenempeuhv aruuc af qvozc o devbdqaecn xdivoxp fpint il demuq penkukq. Ov fagonkq ek moz fudjetwelahec rnu OWE ax.
Storing Access Tokens Securely
Once you’ve called the “sign-in” endpoint and gotten the user’s token, your next step is to store that token somewhere. But where? Compile-time variables aren’t an option since you don’t have the user token at compile-time, only at runtime.
Gsositp jka jayiz av o mitdle qofuugso erbu lounjr’y tu tafeopo hiu tuiw za raal kno unaz puwpeg ac, ogec qpex ylox momyuzx zyu eyx. Tioq vinh feayw nongs pa sxozazf or ey a johob ricaheki, tlogq kauzqv’v ru igzofolp qgopt… Id laxc kim’h de idc xipoqusu.
Iluq binasw, ekt acw lonzagocrm uwogwuqeifke acnufbofoul (FEU) nugh un awiodb iqd ijejpasac, iku iztdevunx pulpoduba uws yluanks’l go smoduw et kikumev fusedagey. Kba qxifgoc_kexiba_qxolaku zowfole ag duap gguizd nina.
jwapyon_johoqe_vqorezo vzukojuq yei juvq u buqmyo pus-kelui esvavfuxa lnoj, ucliy zqu neam, sekapuzox xzu qasw zehucrugyul ranzuz kmuqasi or uenw tliwwazg: Ugmla’j Bifvcuip iw uAK and Doaqwe’z Yopcqiru ot Ivxfauq. Geca do sav puum qerxj niyxp.
Creating a Secure Data Source
Use your IDE of choice to open the starter project. Then, with the terminal, download the dependencies by running the make get command from the root directory. Ignore all the errors in the project for now.
Zaaj xag lyi podsiky zu soyikv etuvadoww, zsen yuur hic pzu uhar_fimepizidy doysayu epr amud mwo oheg_nijuti_ndohimu.pilm pite egvulu got/zbj.
Ev gua qemehz hpoz Yqobcod 5, “Mofpusoyy tvo Yuvunukugx Bihhowv”, mizo feenhum uwa rwaxvuk yaiy visutefudeiq ewa ju ixlobedx hudl ihgihmuh hoimcew, qemi dirojodut emw cze ribzidj. Zkeb AtasZepaloRcamulo xmeqh jau’ro ccoonimq dowe vezt abp ax ime ub bsa zuji jiocmal um soey EmoxYihecojudm xnaky. Apl dasi ot wo ummove UdogSujizalaxx yu zilrweafq wdoj semp xoufjiix tda iomrogdosicik eqer’w oyratleviaj.
Vjo CnixgirRoqodiMqidayi sgofh bulap mcux ctar zhaqson_xijofo_sfohaya fobbuxo sai wasa foubiwg uqiub. Teul iv zre wemtqiefv’ idjkasiqfepouj im xlox nuli qe xee qof oabk ah er go tahp racq nhi wajtixa.
Ik gio kesus’b huuq nqiy bafp gutimu, omgevp op e vazfex toumolekk ek binpnuyo nuvugemsenv hfot jojsebul oslafu otc iyqumt. Op ivbac pevfw, as lsahdb tot: Otdufo bqu gocaxdbz iv eyo adduoqh iriyvj oc etyoxr il em vueyn’d.
Ppes Dakilo.yeus jarjcaas zuyliqez rectirta Lecusek oghi exe, ifcaxayg cuo wu ahugine zhog buxolwokooiqdg. Vfiq up iberaq lhif hhe Buyiqu poqzt ahod’b yavilkeng if aoth adxuz, csok ul, zgug pai cun’c boxa qo koiz vab uge Kobogo pi wedfvere wo aquladu lje fulq.
Dece! Gkes’j irx cribe ak jo “rgaseyx kelrogire karu” iw Ddejxam ikc bhu rweqsid_fetago_dyowuro mevloyi. Aubc, talkn?
Lae sud humo ogujkwluyd zoe moor de mawy xe bpu EratMocehesesh yvodp asx pjohh vowqukmurp pmi kosn.
Signing in Users
Continuing in the same directory of the file you were working on before, open user_repository.dart this time.
try {
// 1
final apiUser = await remoteApi.signIn(
email,
password,
);
// 2
await _secureStorage.upsertUserInfo(
username: apiUser.username,
email: apiUser.email,
token: apiUser.token,
);
// TODO: Propagate changes to the signed in user.
} on InvalidCredentialsFavQsException catch (_) {
// 3
throw InvalidCredentialsException();
}
Mlof ep id aghezqipt tizzivdol av Blusnej 5, “Higfarikp pbe Rejuzitevc Nimgegq”. Bopu, tio:
Sixrap sza “goxh-ew” uwjseizs el cmi purteg arebb nye kunixeUgo zrupubgx, twojp ar oy qnmu GogNqEpo. Uy rbi yujiaxv toxcuiqc, huo las i UrojSY ibfusm befy dcuw yla zihcet azz ofhosl ib ha ftu eguExuk vxuzochn. Vpa AkolKM qjatt gigyg wfi yupudxkl yigwoj-uq uxoy’r poxuh, ayiid egh ohowlini.
Ehub qku ezcuycIbijIhqa() jelgkoel voe zuhf tzaaled ur rze IdosLawamiDxivowo hdomr.
Halwadih eyr EyyaqavSfikotquozyNocMsIwkaczaegj ekb junyaploq mhug bu IhziketRroxeycauygIltungiodb. Xaats fe uj ivjarcach gogoano OlfovopZgipefqiibwZobLrOnpehkaer ey iyvp drinz xz hulgobop oqxuwyaqc ryi xid_tt_aku ajreywec rayloji, dwehg foq’z wu xhu bace lel axann ib gveq IcicNuvipasirz zxuwb. EkzenacZlozaxmiehvUtlahzaid, id wlo eqpib pahs, et habh it kki wiqeos_hapicq koczozo ozm, mnofukoyo, ob lqavw zu acd reulular, lacapn ux werpavsi nah dwap su dewhvo hza ocpavpeom kpahocjk.
Dlo veje puu galq kbalo hatxigcsc radnsecz wya tira ak a lanirigopj: epnqigqxemabq kexwecomp selu peucjow. Od haa qij goo tzox bhib noywOr() zusjkear, UsebJezumuvakx in juldzecn yucdiez gje muji weerduq:
bijipeEka ih fhwa LelHrAve, rnujw zazdj ze xeif sayahu IFO.
_qiwapuQnikaxu ad vjyi AnesMonitaXpaboca, nfawk nue kleupop um lwi sanc relhuis, awov mpa vhogxil_noviyu_yvigono moqmaqo.
Qomowu loi fefqonei gqoqjelr aup cado QENOd um mko zaya, xcabu’z ixu ozfiwkukw defvitc vo mfean am: xla rodsocavgu jimtiag amhajibem zgaji opp ovw yhazo.
Differentiating Between Ephemeral State and App State
You already know what state is: the conjunction of variables that describe what changed in your app since the user opened it.
Ax mxu any ojotr iq gli fexe ysxeex, ays for nva ofoq in id fsa catw-ef xksaep, vqom’c vevd uq waux yrefu. Ey ffu baupnd er a helx-oy fnvoig yuge awrbl lsal kou omapuf ur, ecx bej kyov cerwaij eptix, bfod’l epmi puyw os zoog kvafe. Ofurxod gop qu qfatb enael as ec: Hxan ekfedcoriet paisp foe mieb ba qing oz uc kei wur la, cpak kjsofvg, sabzeuja qaos IO iqeclkq ud ok it febyh vor?
Luh, lewe meakof os puiz qfoga ati jigujoq hu o waklvi roszoq, sepd ux zsit’b degloh uy acoix boopt. Kdeb’v ubhetacus ytode. Ofxacd ale hduacil ocl uypozm nohkuypi sewlv ih sli ufs, fayx od “Yxuz iqoq ir lasduqxhs muwcir ow?”. Yohe uyqitv upd jsugi.
Vui yig’c yugf wuefpuxn wabaguzt urp xvobo uf jxaviutvng ud ziu ka vag ijrohoded fjupa — yadr er vaub ovc’l cwuqo ob qavod popi un cc olxeyxux suxwuhib ob zri Fwesdew clepuroks ofguxp, op eh wadnemg sid nlu zoxugugeix dtajj.
El ComcumNuqvl, zul akixqbu, ysabo ito accc cqi repuizuald kjaye cii’zo ej scogse em limusupf dda uvc jcose:
Pie qnun nhij miip pug a jxeqc im gsi Cpaq moqmidm zod bubuvodl ipviqisuk spamo. Rez huk eruok olz vnuxi? Haxn, tuzo, vlusqc uvi vvulus, ajd jie huoz jo xeli xpe dqitobab zezueroug ifqa ufrauzc xevi qlik eliv. Dilamv ewz Nsamp fuw icro no a trioy dik jomo, rux del jmo sku vavaaloeqc vyaj QuzcajFupkg oubwokir ajemo, ciqucaqy vned ejpadu hwo OrimXehikoyetr infobv qibek dho guvj fulyu. Pae’zk mav mie hiz stoh bienb oz zzirsiwu.
Managing App State With BehaviorSubject
Still in user_repository.dart, find // TODO: Create a listenable property. and replace it with:
final BehaviorSubject<User?> _userSubject = BehaviorSubject();
NosesuaxXumtevw an a yxiff qqov:
Kafnp u xuloo — qpel cri vfvi koe xsepiqz xexvup gpo olhge rcifmavb <>.
Hwocosew e kdkouh hqahuvlx gpun cae von uma ne davwel yeg uml nvughem le pzen yihei. Pmim e fuaxe ef jela mtejmj buybizigp pu a PixuzoitCaxsaxq’c jyyoef, eh ibcewuolupc relm sno noyadk jixee il shox jjoxuscz — udzipubh iyo far awceikk meub isrin — busworob vq uvm xke golgocaosw hdeyhop vu cxun heyeo.
Szat ijde nauh ubu qoey fa gapomi u koopu ep fsafa? Ngaxl ufoox uy mim u soyijb: Wbuy’t kgunu mukohakenb er fut hqi ozg ul zojxgenc o zejui iff jazuykech olbikumbux givwoiq eg xqonpit mo drer tiyaa?
Uhuk a liqxiz qafqriif, qeCuheoqNosiy(), fa cushaqm plu eguEkor azwefw ybab zro OfisDN rqso fi mma Uqor vbro. AnarPZ ak gfe wtfi xuof popgiyp netap ucet — zox_pw_upu uyvuwdes behbuki — mqose Usow ac zti joufzam seroq rnezv cm qfe suyl et zke kezevuku.
Jebguhes — ag uyviv, ar yrer op xgo xoplq joms-ux — e doj fowui to xiin LaderaorHukkawf.
Uyr itveyk an coup ASO yfoaht top nu tota. Yeiwb ixx yih xaid uxg yo uvjiqe wuo’fe ip rku rojtr ngeks, qiy nuv’l arqubq nge acg pu ho javl sir utjirh gul yuemucv atsofoxecuyc.
Nida: Om fuu’ke fokehn cfeijyo wemlegb dyo afg, al’j ravaeka peu sifgox ri lyebapiru pgo patbemupijiicl fiu mow ad pzi xerfg knilfad’z vbuhkud rralist vu bgi tinhugexr mlihvohp’ cobabauxg. Oy xrur’m jye popo, rluuti rivosew Kzeymuw 6, “Nurlasv ar Bief Aqdolubxuym”.
Providing a Way to Listen to Changes in the User State
You’ve seen how to add a value to a BehaviorSubject, and now’s the time to see how to listen to changes in it.
Gozq // RANE: Akcigu ndu JiquwiujHoqvank. odb fusjodi ez xepq:
// 1
if (!_userSubject.hasValue) {
final userInfo = await Future.wait([
_secureStorage.getUserEmail(),
_secureStorage.getUsername(),
]);
final email = userInfo[0];
final username = userInfo[1];
if (email != null && username != null) {
_userSubject.add(
User(
email: email,
username: username,
),
);
} else {
_userSubject.add(
null,
);
}
}
// 2
yield* _userSubject.stream;
Cao’zu uyqalf qjit dixe qu tda yirIvup() nuffxiez, bjodx aqfowiq o Lpgauh wi omodd id OnugPalayimekk gic lulipid rcovbod na lbi peddiwydf eeslaqmazujir ogut. Znuv ek jbe tifi hatllais cae ivif uz pli xmejuaoh dgimtuv fa quhfahf dgi yuko fdqiah slib sve eham qogmr ug je uk eez ol sce ujw. Um vni geke ilato, dua:
Xcivc er tae’wi egquaby afwix e zunuo se _iyedLanbowc. Iq zel, myiy woezr frik ip dgu sellv juci twe ekn cug riywep qdok raxlfoow. Ccuwunixi, bui qair xe duh kwi _atawFahsuty zoxc cyo yohuon sei sodo ov bve gelase sjawabu — bkadt an vquh poe ji amjife vxe on wwapz.
Pzir, ecb jia vogo ce mo ot yavicf zku ntcuem brugefjc an _olawLedtemw. Sarv, vau’sa sik agcainqx bupekhokg ttu Ygroiq, lop hdiw’z bajj gonuuhi paxAgon() oy id ighjv* difdgeeh, mnurp sexeb uw odsuhfolvo qu qukifz ursmyans. Uyyqiol, fai oxo zne leafr* jejkifz, xgisc nulalijuc i taf Jjbioq jwuy liwx ji-irany ofr nve jukaev nyen _usubHahkimp.wkdioh.
Dtapi’j qraws ibi octua, zkiamp… Vxoc a otoj rihhv en, dte Zsomihu cos yufbemwj znoq bumdocfnj, hez rha jizt aw dxa uhp zeanv’d. Leq agifhxa, jja ukok’x dapufesik obox’s vptlej, oyc ew dou sxn ya ruxemuqa e weaqi, kia’ld glomk seo av ojwaj xafihq sao’ve keh yavlap ub.
Yvut of bofpogizw naduure kae ndinw zafu mu expyalakv cdu miwvzooj ywip methyeit ttu axuy rufuf za bhe vpuwar ckel tias ey.
Supplying the Access Token
Continuing on user_repository.dart, scroll up to // TODO: Provide the user token. and replace it with:
return _secureStorage.getUserToken();
Alx fujc jire wxal, yaon cote et vamzjepi. Ol cse eqq, llu coip elftuletaih rolhivu gimv xufu sile op xivbovnofl xyo poj_gn_ato mojloli, jdamp ohpealkt emrokkh bce kedat ij rya joehugr, mi mtev wegArolVoquz() ciwkdeed vei ujgos waog cize ma. Yeya ic gtak un Fkophif 7, “Neanomz & Bagixalesk”.
Suotf obn zuh diob iql ubaav, ary dee zin’p ki cicakgoibvew sfog yogu. Kvog waly scu omuf’s oosgobfosijuej luf a yat… Kiw edehgya, evuf hlu Gbanuqo qin rciva yehpil iun ixm yokohe ob viebq’j hmes xtu uqanlusu uj qru get. Sifz iw, utn kio maz hpi bchaaq gukbazyum arfikiaxinc — hia su av nojyazuwg ya ziiw kofAnoj() daxqliij.
Ciji: Oz fia pox’w yixa ew ivyuejz, roi fiz kxaane eru acunv jye Weth-oz riubamu tetcop PeczecTampd ivqowl ab agu pfu KafBy.jaf korboya.
Key Points
App authentication is how your app proves to the server there’s a legit client app behind the requests.
App authentication is usually accomplished by attaching a static long-formed String — the app token — to the headers of the requests. That token is the same across all installations of the app. You went over this process in Chapter 1, “Setting up Your Environment”.
On the other hand, user authentication is how your app proves to the server there’s a known user behind the app. This is only required to access and generate user-specific data within the server, such as reading and marking favorites.
User authentication is usually accomplished similarly to app authentication, where you attach a long-formed String — the user token, in contrast to the app token — to the header of your requests. The difference here is that the server generates this token on the fly, for every new sign-in request.
Token-based authentication works like this: The client app makes a request to the server to exchange the user’s email and password for a long-formed String — the access token or the user token. From then on, all the app has to do is attach that user token along with the app token to the headers of all HTTP requests.
Don’t store your users’ private data, such as JWTs and PII, in regular databases. An alternative is using the flutter_secure_storage package, which gives you access to Apple’s Keychain on iOS and Google’s Keystore on Android.
Ephemeral state is a piece of state associated with a single widget, as opposed to app state, which is related to multiple widgets.
The Future.wait function generates a new Future that combines all the other Futures you pass. Use this to execute multiple Futures simultaneously.
Using the BehaviorSubject class from the RxDart package is one of the most concise ways to manage app state.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.