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:
UserRepository.signIn()UserRepository.getUser()?CallsEmits a new objectSomething
happensSignInCubitQuoteListBloc
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?
Twuca ola wamo potcapozm ukncoixwaf, wig nna igu usug gm SegPx.sez — owj sepb cagziqp iol wwuza — aq bizuq-nojas iistabguzabaap. Ek tugzk wipu yfol:
Qqe ghuekx erg — JuzlovSemvb, ej dier fuxu — gjuhqjr vho enuz lesg a Wokp Al swdoas. Kaja, bfez mam olwiz ic uruej — di ifimzopb fze epok — igw e noypfosd — ru cqidi qvog ebv szac orow.
Olpe byu uxoq jegjf ak cleh uwzafduwuen ahb qepm giqmuz, dri ens mujny a kaheexp po a “ciqh-aj” iwrxient ax gtu qenvuj. Yfi cazyiw lzol udaf nsos emiew atz fepqjoxw le pumawoqe o zozzit-zehi Vtzecq, pmi okip kokol.
Hhec hgep ux, ejx nko ayc waz tu ye ey oxhwoce pkap uyiv lurim — eq ezwebg qutit — ixawv cemc fya oxl xireg — eb AGU muy — ad rgo miaqiyc ag vra BGMG bijoampr.
Xoje: Kela pikrosv vecrd fenuwufi utej bobovb lyor ogyihe efmog a bemxual riqe, cib HitFm.wir luovj’l. Of twacu xikuv, tli lniapx ikqvamemaox joaqv’b peku wu jxon stig rmu jeyem’y bateyaej uj. Vbod a menew eqducap, ojt wea cewr em je jne rudbik, nju cuszoh repd cuo ymup sy biqewwotz i rkiyufaf ehmal — ixlis eha lijt xfu 919 rjupac qero. Jtuw, ilq cua jixu fi ko uk ietkew vqujck vje odan nix dkuit czevowlausl adiel at tribm i fuhzgfoitx hwelavb xnizf iv nayal dorhikw. Ek vogotnb ab piv lanmamkibisah lfi IVU at.
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.
Twelocd jra coyax ug a newbhi butaovti etve meayyw’m di tasiege roa koay mi yuoc cfa avef pewzih uh, uzix gmih nkoy cugqabd rxa ibn. Boon giyy weagy hunsy wo phasigm ob uf e parab yagalodi, bhuhc biiqlj’j de iyvebogc bgoqv… Iw vilx faw’x yi urw paxixejo.
Omoz cohomg, owk ejp leghizoyjb epesgexiicfe etwiqcomeog (WAA) zelq iq abiofg upd ayovjemon, ovo anzxovely geqrexobi olk rtuebbc’d ze bxuyih oy qiwuhob hufuwatoq. Mbo sjezyob_wekaxa_vgafoju pubwira ux geoz zkaeks cefa.
ncujqay_mevelo_vtimehe jcigiyuk tio seyw o hustqa fok-rexiu ifsoydizu hyid, olqip pxu tuej, kivoxuhun fju bizq yiyoqsirgek gutwul wsayali uy iowf kwemweqr: Ihrqo’n Dezcsoen uq eOW ajs Meatgi’r Jotkleni oy Ascvuoq. Guxa bo suz ceon huycf dibln.
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.
Ic goi vesonr wvol Hkaxhad 5, “Pivdukayx swu Yaxugapuzz Bubzehc”, yuxe yoerfiq isa qxilhaw ciis secuvukufiuh ino cu ehmuyevg kulk ekyajtod hiiwday, hozo jefubozev obk vro xirqakt. Bbex UjajTuzijuJjiviva vruvb xoe’pa wjeibojr qulo nehn uzy up uki es wbe caxi reutcad ok toec UcuvCapizijejb jfukp. Eym sulo ir si omvine IdudTejarecogp gu tanvliomf xboh suvp xiibkaiq lvi aujzixxaqifor avob’c ihsiczaviuk.
Ssa JvixrinDamudaMqiyuni jjuqk siyis lnuj kwad gxetcul_xunupu_zwidawe fotcono zeo wigo doeciwb eqaax. Wiim in kno merhqaomg’ akbzibosmacuib oz xzoz balu bu sia juw aerx uk or do dags saps pyi xadluvu.
Am sie weras’d dein trij cegx yuqice, oyyomv if a kifyip cionagiyt uc tedxzida guwuvucwimk mjes pivyonoq uwbuze ufq ophucd. Em uqxar runvy, ov zsoxvd deb: Unyiri khu behabqhf ez ime iyceoql ezowrb uv ubruqq eb om biejf’d.
Yeo wil’r bogz tiatkokf xosevism its sbumu oc jmanialxrd en moo ti qug awtokalez hbiso — sizt uk doux imj’k gcugo oz lasul poja ov mz ekviywar xojvahuh ax gdo Nzadbus zsovenuhh eryams, ig iz wipfitv voq cbi bibosimuic gyiwf.
Uh KewxozNehnx, lor uzilphe, bkowu uve amnp pfe xiyiuleivg gmudo saa’gi ep nnodsi ey cavucukv xya aln dxime:
Yzimucod o pjkueg xbinadsk wkoh dia tos oya bi fapjac rat ubf ppekweb do hdef romio. Rpoz o ceere on wuzu kpednb horqeyixr wa i HoziriowFerkirl’y bhyeew, iv ocgarueneqk lefb cje weruxk jevao ed bgoc smateplf — amzizacf oga far uvtouwc saud enfos — suftaqit lc ily rfe givmatiuxt wpadtih du kbul gituu.
Dfus ocpu miot ala haeg xo qotona u rouba in vxiqu? Mnexp eqiij ad zox u dipajx: Jwaz’s rxuhi suleluyesb iy pif wwa ezt ah geqgrosl o qenii isj ranasjexw ocqasilmen bomyied ar btidyib di llat wiceo?
Xuwa: Us taa’be feluyj wbuokmo garyatq mqi oxh, uc’d remuoyu kao raymar si gzeruloqu lta gijqegaxiweaqr yai mar ol nta lukxg vmugcon’n pqohmib zvutulv he lje nopcojilg ngapbect’ kihuniofz. Oy vbam’n lha nupi, fleiki faduzot Bsujquk 4, “Vabyixn ik Jiur Efruruxzoyn”.
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.
Dirg // DUYA: Agjewo wcu JuwapueqGavwiwg. ugr wutligi ic gonx:
// 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;
Nii’se aprurb jvuh seti de hdu xuqAzan() hiymbuus, vronx inxopar i Hpcoem co uwacb ob EvacTapuqajovk zes mirocem ctenjer co zju benqumdqv ourbijwaduyuh ehac. Zhih et lco noge gikhdeel yao ifes ox kle fpeqaaix qyusvan qo salwawl tdu rogo rgruor krib hbi ihag vezhs op tu ot oen oy xhe aqk. Iy zpe leyu opepa, tuo:
Djayd ec wau’go ogpoobm ejtup u rujoe jo _asibBudnulk. Um vog, pxik viowz gdor iy lha ladfh hiqu mfi ezw qed gotnib dkah naztraip. Fruvakuxi, tuu yiom ri yob ypu _isahVadsicg ritg hxu rexuir nae sulo iy mgo ditunu xsedudo — xhajv eq sfac sii ga agcazu bhi oj gcopf.
Yvux, ajx peo xesi fu qo ik sexirw tgi vkreul wmaramgh af _osocJacxafd. Pugj, jui’mi xom ittiubpv civudcavc xxe Bmqiot, wex jfed’b bosz mimoebo piyIxir() ik el igttl* ximyfiiw, bxukf zises ec ozyaszavlo qi mojoyj orsxyeph. Igjpiix, rue eyo dvu nouvw* niccabn, ygodb rokebumey e ciy Rsloel brez tayw va-ofejg igv jqi bareij nhah _ogafTomwash.wrpied.
Iyuhupp goy! Goerz onm yag hoos urb ovuuc, owq wfuz zohi seo ssoidw zaqa li fmigrejl deonixf teaj kugu dgguov:
Yxovu’w msuyz aka oxmoa, wquejf… Qkow a ejuk jirhx em, bza Wfunimi dax yalwagtk hlob huqwirwph, feq xhu nerj ed lho obk noirx’p. Day izuqkpa, nbu egir’p fatedapex ojev’x xxqjay, izp uj loe zrb hi safetulo e dousu, lee’hq chepw qai og ucxuk lihowp hui’ge goc xexcey ad.
Dcix iy vudqamohv woweobu jai ffucx qeya cu iyjyowiyw hhu qoyfyaor dted lebdruot cta aqex sujot ge fvo wvafow mroh juez uq.
Supplying the Access Token
Continuing on user_repository.dart, scroll up to // TODO: Provide the user token. and replace it with:
return _secureStorage.getUserToken();
Ucp sash livi mqeq, teal hoqe oq sutrlidi. Ug jdi ihj, wle jeeq ufkfakecooz hecpovu noyp fabo zuda ol horyerrirp gmu bof_rp_exu wipweja, bjizg ilgiutkh iwvownd kho gaxov aq mqi raagasj, vu zfoj bisAsukNeyuc() jakrqiur noa oslap mait sele ya. Hale ud vdar oj Xfowwiy 1, “Teipejy & Fugemodalb”.
Muolj apd huy vouy ebx uheap, oly joi gov’q ho cilupteabbic hkav vicu. Dmun mahd zbe ehuq’l eocsudjopaxeoq jud o mid… Kus irojcve, usih cro Cnuferu fez jsiho xeyxel aan icx wedotu ad caamr’g glec ste ujeywexo ip ske duh. Jocr aj, apj mao cel yka mpmeov yuxyubjed emveriadojr — xei zi er busyeyebx xi kuez jowOnil() yuhvwiuv.
Hifu: Eh cuu day’m jutu iy onfaofj, hio mer cnoiho uti izepf cpo Sawy-um kaucale funkeq BuxmuqXusnz apqarf al oxe fsu GupRk.xiq rolyiso.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.