Closed Runi-VN closed 4 years ago
Camillas comment 1 Rewriting following chapters:
I dette afsnit beskriver vi hvordan vi har valgt at håndtere hele vores login strategi i forhold til real world security risks.
Bcrypt er en hashing algoritme der er udviklet specifikt til hashing af passwords. Den er derfor designet til at være langsom. Grunden til at en hashing algoritme bør være langsom er, at det vil tage lige så lang tid at brute force hvert password-gæt, som når det hashes. Ved også at inkorporere et salt, er der beskyttelse mod rainbow-table angreb, da to ens passwords vil resultere i 2 forskellige hash. Herudover kan antallet af rounds justeres, hvilket gør processen mere langsommelig.
Forsimplet diagram over hvordan bcrypt virker: Rounds er antallet af gange hashingalgoritmen bliver udført. Første gang med password som key. I efterfølgende rounds er det skiftevis salt’et eller password’et der sættes som key, mens der bliver hash’et med den foregående value. Jo flere rounds, jo længere tid tager hele operationen, hvilket betyder at når computer hardware i fremtiden bliver bedre vil denne algoritme stadig kunne benyttes, ved at sætte antallet af rounds op. Dette medfører også at hvis ens applikation indeholder meget sensitiv data, så er det muligt at sætte et højt antal af rounds for at optimere sikkerheden - det betyder selvfølgelig også at brugeren vil opleve en betydelig længere “ventetid” når de logger ind.
JSON Web Token indeholder JSON-formater der bruges som bevis for authentication. JWT har følgende struktur:
Header - indeholder information om hvilken algoritme der er brugt til kryptering, i vores tilfælde er det default algoritmen HS256 (HMAC med SHA256). Payload - indeholder den information der er relevant for ens applikation. Vores payload består af et expiresIn objekt og et user e-mail objekt. Signatur - JWT validering. Formålet med signaturen er at kunne validere afsenderen. Signaturen er beregnet ved at encode header og payload med base64url encoding og herefter sammenkæde dem med et punktum imellem. Denne string krypteres herefter med den algoritme der er specificeret i headeren og vores secret.
At JWT header og payload er encoded, mens selve signaturen er krypteret giver mulighed for at læse header og payload ud af JWT i client applikationen og bruge de værdier der er sat, mens signaturen kun kan læses af dem der har adgang til den secret der bliver brugt til signering. I praksis betyder det at for at ændre i et JWT (angive sig selv som admin eller en anden bruger), eller lave et falsk JWT, er det nødvendigt at kende den secret der er brugt af krypteringsalgoritmen. I vores program er det kun vores server der kender vores secret, så der skal skaffes adgang til den før tredjepart får fat i den. I vores JWT er expiresIn sat til 60 minutter for at mindske sandsynligheden for at det kan misbruges i tilfælde af at tredjepart får fat i det pågældende token.
De fleste JWT biblioteker har følgende tre funktioner, som er nødvendige for at kunne bruge JWT.
I backenden håndteres produktion af JWT ens uanset hvilken login metode der er brugt. Yderligere ville vi gerne have at authentication via OAuth 2.0/OpenID 2.0 provider skete i backenden. OAuth 2.0/OpenID 2.0 login er mere komplekst end vores eget login og vores valg om at det er backenden der skal stå for dette har medført at det er nødvendigt at foretage et redirect tilbage til frontenden (react native app) umiddelbart efter login.
Da authorization headers bliver fjernet under et redirect, for at beskytte clienten fra at blive redirected med deres credentials til en untrusted tredjepart, var det ikke en mulighed at sende JWT på denne måde.
I stedet sættes JWT som en query parameter og sendes med til frontenden. Det blev vurderet som værende sikkert, da responset sendes over TLS/SSL hvori selve query string’en også er krypteret, samt at vores JWT er short-lived.
Vi begrænser mængden af login forsøg en bruger kan lave. Hvis man forsøger at logge ind mere end en gang i sekundet vil det blive opfattet som et brute force angreb og den IP forsøget kom fra vil blive afvist de næste fem sekunder. Hvor længe en IP er bannet og hvor lang tid der kan gå mellem normale forsøg kan selvfølgelig justeres - vi valgte relativt lave værdier mens systemet stadig er under udvikling.
Når man laver en ny bruger, (hvor man altså ikke logger ind med OAuth) bliver man bedt om at taste et password ind 2 gange. Det password man angiver bliver tjekket på en række forskellige punkter:
De fleste kendte svage passwords er kortere end 10 tegn. Vi kræver et password, der indeholder mindst 10 karakterer, men hvis brugeren nu ville have brugt qwer1! som password, men får at vide, det er for kort, så kunne brugeren bare tilføje flere tal: qwer123456! og så ville passwordet være langt nok. Derfor siger vi at ens password slet ikke må indeholde sekvenser, der passer på et allerede kendt svagt password. Passwordet qwer123456! ville altså blive afvist, da det indeholder qwer1234, som er et kendt svagt password.
/auth/jwt
Dette er vores eget login der eksekverer passport strategien local
(se næste afsnit om Passport). Endpointet kaldes fra react native app’en med brugerens username og password.
Password tjekkes med Bcrypt (se afsnit om Bcrypt). Til sidst genererer dette endpoint et JWT som så sendes tilbage til react native app’en.
/auth/google
Dette er vores OAuth 2.0/OpenID 2.0. endpoint der eksekverer passport strategien google
. Brugere logger ind med deres Google konto. Dette endpoint kaldes fra react native app’en. Herfra redirectes der via passport videre til Googles servere som står for at authenticate den givne bruger. Når dette er gjort laver Google en request til vores /auth/google/callback
.
/auth/google/callback
Dette endpoint eksekverer også passport strategien google
. Ved succesfuldt login modtager dette endpoint en authorizationcode fra Google. Denne authorizationcode bliver via passport herefter sendt tilbage til Google som sender accesstoken, refreshtoken og profildata tilbage til os. Til sidst genererer dette endpoint et JWT som så sendes tilbage til react native app’en.
Passport er en authentication middleware hvis formål er at authenticate requests. Frameworket indeholder en masse predefinerede strategier for login med diverse OAuth 2.0/OpenID 2.0 providers, samt andre BasicAuth, JWT og deslige. På definerede endpoints kaldes passport.authenticate()
, der tager navnet på den ønskede strategi ind som herefter eksekveres.
Passport sikrer den korrekte struktur på URL'en til OAuth 2.0/OpenID 2.0 provider, og sørger for at client id og client secret bliver checket. Desuden håndterer passport svar fra provideren og behandler det for os. Ved brug af passport sikrer vi stabilitet og pålidelighed, da det er et gennemtestet og udbredt framework
Vi valgte at vores primære login strategi skulle være gennem OAuth 2.0/OpenID 2.0. da vi var interesserede i at prøve at implementere et så tæt på real-world scenarie som muligt i forhold til vores valgfag Security.
Det er en meget udbredt måde at håndtere login på og det abstraherer hele håndteringen af kryptering og opbevaring af passwords hen på den OAuth 2.0/OpenID 2.0. provider man vælger at benytte. Dermed ikke sagt at det ikke stadig er nødvendigt at sikre sine data - vi håndterer bare ikke længere passwords.
Det der skal beskyttes ved denne strategi, udover sensitiv brugerinformation som f.eks. e-mails, er den client secret vi får udleveret af vores provider, som i vores tilfælde er Google.
Denne client secret gemmes hos os i en .env fil, som aldrig kommer med i versionsstyringen. Når projektet bliver endeligt deployet på vores server, kort før Fullstack Javascript eksamen, bliver filen manuelt lagt over på serveren. Hvis en tredjepart får fat i disse oplysninger (ved at skaffe sig adgang til vores server - håndtering af dette er beskrevet under afsnittet Servere og Databaser) vil de kunne udgive sig for at være os over for Google og eventuelt få fat i brugerinformation, requeste adgang til bruger e-mails med mere. Dog vil en tredjepart også skulle kunne komme ind på vores konto på Google Cloud Platform for at kunne sende data’en et andet sted hen end det endpoint vi der har specificeret - serverIPaddress/auth/google/callback
.
Vores OAuth 2.0/OpenID 2.0. flow er et authorizationcode flow og virker på følgende måde.
Brugeren trykker på Google-knappen for at logge ind, hvorefter der bliver åbnet en webbrowser hvorfra endpointet /auth/google
kaldes og eksekverer passports google
strategi. Denne strategi redirecter til Googles authentication servere, som resulterer i at Google beder brugeren om at logge ind.
Den request backenden sender til Google indeholder de Oauth 2.0 parametre vi er interesserede i, scope
, accessType
, prompt
og state
.
scope
er det brugerdata vi er interesserede i og her specificerer vi at det er brugerens OpenID 2.0 data samt deres e-mail vi gerne vil have adgang til (også kaldet profile data).
accessType
er den parameter der fortæller brugeren at vi gerne vil have offline access til deres data, hvilket vil sige at Google giver os et refresh token med tilbage, sammen med access token og profile data.
promt
med value “consent”
er påkrævet af Google for at vi kan få lov til at sende accessType: "offline"
med.
state
er data som Google sender uændret tilbage til os igen. Vi skal bruge indholdet af vores state for at kunne lave et redirect tilbage til vores react native app.
Det passport sender afsted til Google med ovenstående parametre ser nogenlunde ud på følgende måde
Når brugeren er logget ind sender Google et svar tilbage til endpointet /auth/google/callback
med en authorizationcode. Den vil se ud som her
Denne authorizationcode bruger backenden til at spørge Google om access token, refresh token og brugerens profile data (som blandt andet indeholder deres e-mail)
Som så kommer tilbage nogenlunde sådan her
Backenden genererer et Json Web Token (se mere i afsnittet JWT) og redirecter til react native app’ens custom scheme (Brugen af Expo, deep linking og URL schemes). Dette lukker den browser der var åbnet op og herefter tager App’en over og sørger videre for håndteringen af JWT.
Det der adskiller authorizationcode flow fra implicit flow er det trin hvor Google afleverer en authorizationcode til backenden og backenden udveksler denne til access token, refresh token og profile data. Det vil sige at implicit flow går direkte fra bruger-login til at få udleveret accesstoken fra Google. Vi startede med at lave et implicit flow direkte i app’en, men dette vurderes som relativt usikkert i forhold til authorizationcode flow af flere grunde. Client secret skal for det første gemmes et sted i app’en, for at blive sendt med til Google. Herudover sendes access token direkte tilbage til app’en gennem browseren og app’en vil selv skulle holde styr på det. Dette vurderes generelt som værende usikkert, både fordi al clientkode ligger frit tilgængeligt, trods eventuel obfuscation, samt at det generelt er nemmere for en tredjepart at få stjålet et accesstoken fra en app eller en SPA, end fra en backend. Dette kan f.eks. ske gennem Cross Site Scripting hvor der injectes client-side scripts.
Authorizationcode flow’et hvor det er backenden der står for kommunikationen med Google, udveksling af tokens og opbevaring af tokens er den anbefalede måde at håndtere OAuth 2.0/OpenID 2.0 på. OAuth 2.0 Flow
Der findes fire typer af flows, eller grant types som det også kaldes, for en client at få et access token fra en authorizationserver på.
Brugen af custom URL schemes, såsom appname:// til at linke internt i app’en er ikke altid lige sikkert. Hvis to applikationer bruger samme skema, er det for iOS ikke garanteret hvilken app skemaet henvender sig til. For Android har man mulighed for at benytte sig af Intents og samtidig giver styresystemet per standard en valgmulighed mellem de apps der har registreret brug af skemaet. Denne artikel fra Nowsecure.com beskriver hvordan at at såkaldt deep link abuse virker i praksis, og hvordan man også kan beskytte sig ved brug af en ekstern link-liste som indeholder en checksum. Sådan et angreb afhænger dog af mange faktorer. Typisk skal appen reverse engineeres og en lignende eller tilhørende (understøttende) app skal udvikles, fungerende som en trojansk hest. Dette er givetvis nemmere på Android, hvor man frit kan installere .apk-filer udenom App Store.
For at kunne sende besked tilbage til vores react native app, som beskrevet i OAuth 2.0/OpenID 2.0, er vi nødt til at redirecte tilbage app’en når Google kalder vores /auth/google/callback
endpoint. Da vores app er en Expo app og vi ikke ender med at ejecte Expo, er vi begrænsede i de muligheder vi har.
Vi har valgt at bruge Expo’s egen anbefalede løsning. Det vil sige vi bruger Expos scheme exp://exp.host/@yourname/yourAppName
, da det er den eneste måde vi kan linke tilbage til vores app på.
Vi undersøgte muligheden for at lave en embedded browser i app’en så vi i stedet for et custom scheme, ville kunne lytte på et specifikt endpoint i backenden og trække JWT ud fra den request. Vi fik dette til at virke, men fandt derefter ud af at Google har lukket ned for muligheden for at bruge OAuth 2.0/OpenID 2.0 gennem embedded browsers i 2017, da det er for usikkert. docs.expo
Asger's comment Rewriting the following chapters:
Vi har valgt at have 2 databaser i dette projekt. En MySQL database til opbevaring af vores brugeres login informationer (de brugere, der ikke logger ind med OAuth 2.0) og bl.a. refresh tokens for OAuth 2.0 brugere. Den anden database er en NoSQL MongoDB der er hosted online hos Atlas. I denne database ligger geo lokationer og chatbeskeder (hvis denne funktion når at blive implementeret). Den data er forbundet med et brugernavn, som brugere selv vælger (OAuth brugere kan undlade at vælge et brugernavn, men så vil deres mail blive vist som brugernavn i applikationen i stedet for). Lokationsdata er naturligvis personlig, men denne data ligger uden referencer til brugerens mail (hvis brugeren har angivet et brugernavn) eller anden personlig information, der er er gemt om brugeren. Atlas krypterer som standard forbindelser til databaser med TLS/SSL, så vores data kan ikke ses over netværket, når vi opdaterer brugeres lokationer osv.
Vores MySQL database ligger på en anden server end vores backend. Vi har valgt denne opdeling for bedre at kunne sikre vores data, både i forhold til hvis vores backend server skulle gå ned og i forhold til sikkerhed omkring dataen i det hele taget. MySQL databasen kan kun tilgås af vores backend server. Databasen kan altså ikke tilgås direkte af brugere og database-serverens IP er ikke offentligt kendt. Forsøger man at tilgå den, vil man som sagt blive afvist af dens firewall.
For at tilgå databasen bruger backenden en MySQL non-root bruger. Denne bruger har tilladelse til at gøre alt på de tables, der har med applikationens brugere at gøre. Dvs. at hvis backenden skulle blive kompromitteret, ville tredjeparten trods alt kun få adgang til de tables der har med brugere for denne applikation at gøre, og ikke andre tables og anden data, der kunne ligge i databasen. Dette kan selvfølgelig i sig selv været slemt nok - det optimale er derfor at give MySQL brugeren så få rettigheder som overhovedet muligt. For at have en fungerende applikation er man dog nødt til at have en vis mængde rettigheder på MySQL brugeren. I og med MySQL brugeren kan læse al dataen i tabellen, så ville en tredjepart kunne lække al denne information. På samme måde ville det være muligt at læse al informationen omkring brugernes lokation og deres chatbeskeder, der ligger i MongoDB, hvilket naturligvis ville være et kæmpe brud på brugernes privatliv. I den forbindelse har vi gjort os nogle overvejelser (se afsnit om kryptering af data)
Vores initielle ide af systemets design kan ses på nedenstående figur.
Det endelige design endte med at følge skitsen nogenlunde. Vi har to servere og vi kommunikerer med to cloud services; Google og Atlas.
Den midterste droplet på figuren er vores backend server. På denne server har vi installeret en reverse proxy nginx, der sikrer at al kommunikation foregår via https - alle requests til port 80 bliver omdirigeret til port 443 og hvis ikke klienten har en passende cipher suite, bliver de nødt til at droppe kommunikationen med vores server. Derudover afviser serverens firewall al anden netværkskommunikation (bortset fra SSH selvfølgelig). For at tilgå de forskellige data, skal man være logget ind (se Authentication & Authorization afsnit). Vi sikrer altså at alle forbindelser er sikre og at intet data er tilgængeligt uden at brugeren har en valid JWT. Når vi selv skal håndtere vores servere benytter vi SSH, der sikrer en stærkt krypteret forbindelse. Vores servere har ikke nogen unødvendige porte åbne og som tidligere nævnt er det kun vores backend server, der kan oprette forbindelse til vores database server (ud over at port 22 selvfølgelig er åben). Database serverens firewall tillader kun forbindelser på port 22 og port 3306 fra backend serverens IP.
Vi har som sagt valgt at bruge både en SQL database og en NoSQL database (MongoDB). Når man snakker om databaser støder man naturligvis på problemstillingen med injection. Vi har gjort flere ting for at beskytte os selv mod injection og har undersøgt eventuelle svagheder i MongoDBs API. Da MongoDB er nyt for os ville vi være sikre på, at det var sikkert at bruge, så vi har undersøgt og testet injection i forhold til MongoDB.
Der findes forskellige query- og projection “operators” i MongoDBs API som kan bruges til at optimere og forme ens API kald. Blandt andet findes $not
operatoren, der returnerer alle dokumenter, der ikke passer på den query, der eksekveres på ens collection. En anden operator er $ne
der match’er alle værdier, der ikke er lig med det, man specificerer efter operatoren.
Eksempelvis ville man kunne benytte $ne
til at undgå at skulle skrive en given brugers password:
.find({"user": "patrick", "password": {"&ne": ""}});
I dette tilfælde injecter vi operatoren ind hvor passwordet skulle have stået. Operatoren lader os finde al data om brugeren “patrick”. I eksemplet siger man: Find brugeren, hvis navn er “patrick” og hvis password ikke er lig med en tom streng. Naturligvis går denne query igennem, fordi patrick har et password - og vi har nu fået adgang til patricks data uden at kende til hans password.
Hvis der var tale om SQL, havde tredjeparten selvfølgelig skullet strukturere sin injection anderledes. En SQL query svarende til det ovenstående API kald ville se ud som følgende:
SELECT * FROM users WHERE `user` = “patrick”;-- AND `password` = "";
I dette tilfælde sørger tredjeparten for at udkommentere den del a query’en, der ellers havde tjekket, om man havde angivet det rigtige password.
I SQL løser man langt hen ad vejen problemet med injection ved at bruge prepared statements, der sørger for, man ikke kan escape strengen, når query’en bliver opbygget i backenden. Det er også det vi bruger i vores backend. Vi tvinger brugerens input til at være en streng, så det ovenstående SQL eksempel ville ende med at se ud som følgende:
SELECT * FROM users WHERE `user` = “patrick;--” AND `password` = "";
Der er ikke nogen bruger, der hedder “patrick;--” og hvis der var, ville tredjeparten stadig ikke kunne få adgang til hans data, da der ikke er angivet et password.
De ovenstående eksempler er forsimplede for at gøre det nemmere at forklare konceptet.
For at løse problemet i NoSQL APIet skal vi sørge for at opnå samme effekt som vores prepared statements for SQL giver. Der findes ikke prepared statements i APIet så for at lave det, der så vidt muligt svarer til et prepared statement, skal vi sikre at alt input bliver “type casted” til strenge eller tal.
Vi har valgt at bygge vores backend med TypeScript og vores API med GraphQL - GraphQL som framework og TypeScript som programmeringssprog sikrer begge stærke typer og løser derfor problemet for os. Det er ikke muligt at give objektet {"&ne": ""}
videre som parameter til en query i MongoDB APIet, og hvis man prøver at gøre det ved f.eks. at sende det i en streng “{"&ne": ""}”, så beholder det netop sin tilstand som streng og manipulerer dermed ikke den underliggende query-struktur i backenden.
I større applikationer med mere varierende data end det, vi har, ville systemet med stærke typer begrænse den fleksibilitet man ellers nyder godt af med delvist ustruktureret data i NoSQL databaser, men til vores behov passer brugen af TypeScript og GraphQL perfekt.
Som standard bliver logfiler gemt til mappen logs, og hvis programmet kører i udviklings-tilstand vil der også blive logget i konsollen. Til dette brugte vi express-winston med en opsætning skræddersyet til vores behov. Vi har fokus på maskinlæsbart output som samtidig kan granskes af mennesker, derfor har vi valgt et output i JSON-format. Vi logger alle indgående requests til applikation, bortset fra /graphql som ville overfylde logs med overvejende ligegyldig data. Vi logger alle fejl som opstår i applikationen.
Camillas comment 2 Rewriting following chapters:
Når man bygger et API, vil man formentlig på et tidspunkt i processen, gerne kunne styre hvem der kan se og ændre i ens data. Der er to vigtige principper i dette, som man skal kunne holde styr på; authentication og authorization. Authentication er at vide om en bruger er logget ind i systemet, og at vide hvem de er. Authorization er så at beslutte hvad den bruger har adgang til at se og ændre i ens system. Til dette har vi brugt Apollo Link, som er en form for middleware.
Et link beskriver, hvordan man vil modtage resultatet af et GraphQL kald, og hvad man vil gøre med resultaterne. Det er et abstraktionslag der bl.a. kan løse detaljerne omkring fejlhåndtering, authentication og authorization. Med denne løsning skal man ikke lave ændringer i hver resolver, men det er samlet i et lag højere oppe. Resolveres opgaver holdes derfor adskilt, hvilket samtidigt giver et bedre overblik. Man kan kæde mange links sammen, så når der affyres et GraphQL request, bliver hvert links funktionalitet anvendt i rækkefølge. Når en bruger logger ind på vores app via telefonen, så kan de enten logge ind via Google eller via vores eget login-system. I begge tilfælde opretter backenden et JWT der er signed med en secret, og sender med til frontenden ved et login.
I vores tilfælde bruger vi Apollo Link Context, hvor der på hvert request fra frontenden til backend serveren bliver sat et JWT på headers i form af Authorization: <token string>
Derved kan backend serveren, for hvert incoming request, tjekke om denne header findes.
Hvis denne header er sat, så kan vi med et JWT bibliotek verificere dette token.
For at verificere et token, så skal det være signet med den secret vi bruger, og tokens udløbsdato skal ikke være overskredet.
Her placeres token på authorization header, hvilket anses som common practice.
Hvis token er verificeret, så kan vi give brugeren adgang til beskyttet data. Hvis ikke, smider vi en AuthenticationError fra serveren. Man kan også returnere null, eller f.eks. et tomt array, men vi vil gerne tydeligt kunne fortælle brugeren, at man skal være logget ind for at se denne data, så der ikke er tvivl fra brugerens side om hvorvidt der er sket en fejl.
Backend I vores backend smider vi kun Apollos egne errors udadtil. Det gør det nemmere at håndtere dem i frontend, fordi vi ved at fejlens struktur vil være ens, uanset hvilken type fejl der opstår. Samtidig sørger vi for at der ikke bliver delt mere data om fejlen end vi ønsker.
Når vi opretter vores Server, kan man sætte et stykke middleware ind, der omformer alle errors smidt i koden, til en af Apollos egne. I vores kode smider vi enten en af Apollos egne errors, eller en ApiError. Hvis der kommer en ApiError så omformer vi den til en ApolloError, som så nemt kan læses i GraphQL og frontenden. Hvis vi er i et udviklermiljø, så sendes hele stacktracen med ved hver fejl, så det gøres nemmere at debugge de fejl der opstår undervejs. Men hvis vi er i et produktionsmiljø, så sender vi ikke stacktracen med, da vi ikke vil afsløre for meget information om vores interne struktur.
Der findes 4 kategorier af fejl i Apollos struktur.
UNAUTHENTICATED
BAD_USER_INPUT
FORBIDDEN
INTERNAL_SERVER_ERROR
AuthenticationError kan bruges hvis en user ikke er logget ind, men prøver at tilgå ressourcer. ForbiddenError kan bruges i forbindelse med authorization. Hvis en user er authenticated, men ikke har tilladelse til at tilgå en bestemt ressource, så kan man smide en ForbiddenError.
UserInputError er til at verificere input som GraphQLs typer ikke selv kan klare. For eksempel, hvis bruger bedes skrive deres e-mail når de skal oprette en bruger, og der ikke er noget @
i deres input, så kan vi smide en UserInputError med “e-mail”
som invalidArg
, og en custom besked. Denne error gør det muligt at specificere hvilken inputdata der ikke levede op til vores krav. Det gør vi ved hjælp af et custom invalidArgs
felt.
Eller f.eks. et koordinatsæt, som i GraphQL bare er integers
Man kan også tjekke i frontend for den slags, men det ville ikke være sikkert, fordi brugeren ville kunne sende en request til vores backend uden om frontend mobilappen. Derfor skal den slags også tjekkes i backenden. Vi tjekker for fejl så “højt” oppe som muligt i koden, så serveren ikke skal lave for meget arbejde, før den afviser requesten. Desto før man kan validere input'et, desto bedre.
Frontend
Til Errorhandling i frontenden har vi brugt et link der hedder Apollo-Link-Error.
Det link fanger alle networkerrors og GraphQL errors, og så kan man ét sted i koden bestemme hvad der skal ske derfra. Det gør det nemt at håndtere, og refaktorere senere. Desuden har vi lavet en custom “ErrorHandler” der kan tage imod en ApolloError. Vi bruger en Alert, som er en slags popup, til at sætte en besked til brugeren, som de ikke kan undgå at se, hvis noget går galt.
F.eks. hvis errorcode her er FORBIDDEN
, ved vi at den stammer fra en ForbiddenError i backenden, og det betyder dermed at brugerens adgangsniveau til API'en ikke er højt nok til det de prøver at tilgå.
Eller hvis koden f.eks. er UNAUTHENTICATED
, kan man slette det lokale token i SecureStore, fordi man nu ved at det er ugyldigt, og sende brugeren til login-siden.
Og til sidst, hvis koden er BAD_USER_INPUT
skal brugeren have at vide præcis hvilket felt de har tastet forkert i, og hvorfor. Derfor er invalidArgs
feltet smart på den error, fordi det kan nu benyttes til en præcis fejlbesked og bedre brugervenlighed.
Vi gemmer JWT i SecureStore hos brugere. Securestore krypterer og gemmer key-value par lokalt på brugerens mobil. Info der er gemt i et Expo-projekt kan ikke tilgås fra andre Expo-projekter. Desuden gør SecureStore det nemt for os at tilgå JWT uanset hvor vi er i vores application. Og hvis brugeren lukker for vores app, logges de ikke af, da vi kan hente JWT op igen fra SecureStore. Alt efter om brugeren er på Android eller IOS har den forskellige implementationer.
Der findes Authorization i den forstand, at vi skal give en bruger adgang til vores data og ressourcer via deres login. Men brugeren skal også give consent til at vores app må tilgå f.eks deres Google account og deres lokation.
Når en bruger logger ind med Google, føres de til Googles loginside, og bliver så spurgt om app'en skal have lov til at se deres e-mail, profilbillede osv. Når en bruger så gerne vil bruge vores map-feature, så bliver de spurgt om lov til at bruge deres lokation. Dette er Incremental Authorization. At man ikke spørger brugeren om tilladelse til at bruge f.eks kameraet, lokationen, osv., før man rent faktisk har brug for det. Man kan risikere at overvælde brugeren med en consent screen, hvis alt der skal gives consent til præsenteres på en gang når de logger ind. Derfor kan man risikere at miste brugere, fordi de så trykker nej. Desuden kan de anvende alle de features af app'en der ikke kræver consent, selv hvis de trykker nej til f.eks lokation. De har stadig sagt ja til e-mail osv. da de loggede ind. Derfor kan man få flere permissions fra brugerne igennem, uden at skræmme dem væk.
Everyones comment 3 Rewriting following chapters:
Når sværhedsgraden af potentielle sikkerhedsfejl vurderes, er der altid flere aspekter, man tager i betragtning. OWASP vurderer i denne sammenhæng 4 forskellige faktorer:
Indvirkning den lækkede/mistede data har på os og vores brugere samt den tekniske destruktion en tredjepart ville kunne lave i vores backend bliver vurderet som den faktor der vejer tungest. Top 2 på OWASPs liste er Broken Authentication og den ligger netop så højt, fordi den scorer højst i “impact”.
Hvis authentication management ikke håndteres korrekt, f.eks. når en session ikke bliver invalideret ved logout, fører det til broken authentication. Broken authentication er relativt nemt for angribere at udnytte og kan have store konsekvenser for både ofre og virksomheder, såsom hvidvaskning, bedrageri og identitetstyveri. Problemer i en applikation der kan føre til Broken Authentication
Vi ønskede i vores applikation at minimere Broken Authentication ved udelukkende at bruge OAuth 2.0/OpenID 2.0 til login. Herved skal en bruger aldrig indtaste et password direkte i vores applikation, men kun gennem vores identity provider, og vi ville ikke skulle gemme bruger passwords eller kunne genskabe dem. Vi valgte også at implementere et traditionelt login sideløbende med, for at skabe et mere realistisk billede af virkeligheden, hvor ikke alle brugere nødvendigvis har en konto hos f.eks. Google. I dette login har vi ønsket at komme så mange af de punkter der nævnes i OWASP som usikre til livs.
Som tidligere nævnt har “Impacts” den største vægt i OWASPs vurdering. For at få så lille en indvirkning som muligt i OWASPs udregning og for at skabe en forsikring af privatliv i vores service, har vi snakket om at kryptere brugernes data på samme måde som ens forbindelse over internettet krypteres med SSL, eller i hvert fald tilbyde det som en service. Hos hver enkelt bruger ville vi generere et asymmetrisk nøglepar, ligesom vi selv har og bruger, når vi skal tilgå vores droplets. Med denne strategi ville brugerne naturligvis være begrænsede af, at kun de har deres private key, og at deres data derfor i princippet ville gå tabt, hvis de skiftede telefon eller de mistede deres private key. For at løse dette problem ville vi involvere iCloud og Google og diktere, at brugere skulle gemme deres private keys i den cloud service, deres telefon er bundet op på. Med denne opsætning ville en tredjepart både skulle kende brugerens password til vores service og passwordet til brugerens cloud service eller mobil for at skaffe deres private key, for så at kunne stjæle deres data i vores system. Hvis vores backenden blev overtaget af en tredjepart, ville vedkommende godt nok kunne ødelægge eller slette den data, der ligger i de to databaser, men ville ikke kunne lække noget data da alting er krypteret. Ingen andre end brugerne selv, ikke engang os som udviklere ville kunne læse brugernes data - det ville medvirke til en lavere score for “Impact” i OWASPs vurdering og ville sikre brugerens privatliv.
Access control sørger for at brugere ikke kan agere udover hvad det er meningen de skal kunne i en applikation. OWASP beskriver følgende typiske Access control sikkerhedshuller.
I vores applikation er de ting vi har skulle være opmærksomme på i forhold til Broken Access Control primært været vores JWT, samt vores GraphQL. Vi løser problemet med at en bruger eller tredjepart kan ændre i JWT ved at sørge for at det underskrives, både med hashet af payload og header, samt vores secret. Se mere om dette i JWT afsnittet. Vores GraphQL er beskyttet ved at bruge Apollo Links.
OWASP vurderer stadig injection som problem nummer 1 i sikkerhedsverden. På trods af hvor velkendt denne slags sikkerhedsrisiko er, er der stadig utroligt mange, der ikke tager højde for det og hvis et system er åbent for injections, kan en tredjepart få adgang til stort set al data. Svagheden er nemt at udnytte, nem at opdage og konsekvenserne er som regel ekstremt store. Vi har gjort vores bedste for at eliminere denne potentielle svaghed ved brug af typekontrol og prepared statements (se afsnit om Sikkerhed ift. Injection).
OWASP beskriver Security Misconfiguration som f.eks default accounts, offentlig visning af stacktrace, unødvendige features/frameworks/libraries og deslige. Vi har i vores projekt sørget for at eliminere Security Misconfiguration ved:
Når der opstår en fejl i vores backend, sender vi en error message til Frontend. Den bliver vist i en Alert (se mere under Apollo Error Handling), så brugeren ikke kan undgå at se den, og den fortæller bare brugeren hvad de har gjort forkert, i stedet for at fortælle i detaljer om hvad der er gået galt internt i programmet. Herudover har vi været kritiske når vi har skulle bruge f.eks. tredjepart frameworks, hvor vi grundig har undersøgt om det er noget der er meget brugt og om det er noget vi har behov for at bruge.
OWASP beskriver dette punkt som et af de sikkerhedshuller der meget ofte bliver udnyttet.
Nogle af de ting der er med til at gøre en applikation usikker i forhold til Insufficient Logging & Monitoring er blandt andet:
I vores applikation har vores fokuspunkt vedrørende security ligget mest omkring Login og beskyttelse af vores droplets, men vi mente stadig at det var vigtigt at have logging med.
Derfor har vi gjort følgende:
Not allowed to approve but I give this report a 🥇
Style guides:
https://guides.github.com/features/mastering-markdown/ https://github.github.com/gfm/
Headings
Use markdown headings corresponding to the google docs:
Heading1 =
#
Heading2 =
##
Heading3 =
###
And so on
Horizontal Lines
If you need to create a horizontal line, do it by writing
---
with two spaces before/after like this(see source)
3 Introduktion
3 OWASP
4 Broken Authentication
4 Kryptering af data
4 Broken Access Control
4 Injection
4 Security Misconfiguration
4 Insufficient Logging & Monitoring
3 Servere og Databaser
4 Sikkerhed og Struktur
4 Sikkerhed ift. Injection
4 Logging
3 Login
4 Bcrypt
4 JWT
4 Anti Brute Force
4 Kontrol af passwords
4 Endpoints
4 Passport
4 OAuth 2.0/OpenID 2.0
5 Authorizationcode flow vs. Implicit flow
5 Kort beskrivelse af OAuth 2.0 flows
4 Brugen af Expo, deep linking og URL schemes
3 Authentication & Authorization med Apollo
4 Apollo Links
4 Apollo Error Handling
4 JWT sikkerhed i App
4 Incremental Authorization
3 Refleksion & Konklusion
4 Security relevante funktionaliteter der ikke er implementeret endnu