Newsgroup: it.comp.www.php
Titolo: [Lungo][Incasinato] Autenticazione centralizzata

Tempo fa cercavo di capire il funzionamento di oAuth e di come implementarlo al meglio.
 
Non ci ho capito praticamente niente, ma forse ora ho le idee più chiare su un progetto simile, quindi ci riprovo.
 
Scenario: 
 - applicativo da CLI (realizzato in PHP e, volendo, modificabile dai futuri utilizzatori quindi può essere considerato come "insicuro" e nessuna autenticazione deve avvenire al suo interno) 
 - almeno 10 "servizi" da dover interfacciare tramite CLI, tutti su server diversi e domini differenti
 
Ora, ciò che vorrei fare è qualcosa di questo tipo:
 
 miaapp login --user=aaa --pass=bbb
 
ed effettuare un login su TUTTI e 10 gli applicativi (sono tutte API REST), in modo tale che qualunque sottocomando del mio applicativo CLI sia automaticamente loggato, esempio:
 
 miaapp comando_app1
 
l'applicativo dovrà eseguire il "comando_app1" sul server8 (il come non è un problema) autenticandosi automaticamente con i dati forniti in precedenza.
 
Soluzione semplice: 
 basic auth verso il singolo applicativo ogni volta che viene eseguito il comando. Tempo di sviluppo: si e no, 5 minuti, tutti gli applicativi hanno già la loro gestione user/pass
 Vantaggi: 5 minuti di realizzazione
 Svantaggi: X account utente da dover sincronizzare ad ogni cambio password su Y server applicativi (Y al momento è 10)
 
Soluzione mediocre:
 basic auth fatto verso il singolo applicativo, il singolo applicativo autentica verso un database account condiviso da tutti. 1 singolo posto in cui tengo gli account, X applicativi che autenticano. Tempo: 5 minuti come prima ma un database condiviso da tutti gli applicativi.
 Vantaggi: 5 minuti di realizzazione più un db condiviso
 Svantaggi: boh, nessuno, a parte la totale mancanza di ACL e permessi vari
 
Soluzioni complesse:
 openid: centralizzo il server di autenticazione, i 10 applicativi invece di loggare "in locale" verificano l'utente tramite openid. Non so come, la richiesta di esecuzione comando nasce da un applicativo esterno quindi tutti i vari url openid si vanno a far benedire
 
 oauth: idem con patate.
 
 
La soluzione semplice non richiede ulteriori spiegazioni.
La soluzione mediocre è quella che al momento mi sembra la migliore, ma come potrei gestire delle ACL, dei privilegi utente o eventuali 2FA/MFA ?
Magari in futuro potrei voler evitare che l'utente loggato possa accedere ai servizi di Applicativo16. Se tutti autenticano verso la stessa base dati, tutti possono accedere a tutto[1]
 
Le soluzioni complesse forse sono tecnicamente le migliori (in quanto progettate appositamente per fare ciò), ma paurosamente complesse da implementare, voi cosa dite ?
 
Suggerimenti ?
 
 
[1] POC:
potrei evitare di utilizzare un database condiviso ed autenticare mediante un servizio REST creato adhoc in un server da login. Esempio:
 appcli autentica verso app10, app10 rigira l'autenticazione (con i dati forniti da appcli) verso serverlogin tramite una api rest che risponde 200 o 403, punto e basta, questo mi permetterebbe di filtrare anche la sorgente (in questo caso app10) direttamente in fase di login.
Questo permetterebbe di realizzare anche una gestione a "token", dove i dati user/pass vengono inviati solo in fase di login. Se il login è OK, viene generato un token corrispondente ad una tripla: user:ip:expire
Tutte le richieste successive saranno autenticate mediate il token, senza far transitare user a password. Automaticamente si avrà una maggiore sicurezza (user/pass transitano, in HTTPS, solo una volta), un filtro su IP cliente, una scadenza per la login.
 
 

 
 
Ma devi fare il login o devi autorizzare le operazioni?
Cioe' tutte le volte devi avere dati "personali" o una volta che l'app e' 
autorizzata puo' fare quello che vuole?
 
 
APP locale ho un token valido? 
 
Si:
Metti il token tra gli header della chiamate REST dell'azione richiesta e 
verifichi che il token sia valido.
 
No:
APP locale chiede un token al server dei token passando user/pass o 
quello che vuoi, una volta che ha il token lo usa per ogni REST 
successiva.
 
L'eventuale scadenza del token la puoi gestire nel server dei token.
 
 
Se hai bisogno di autenticazione ogni volta:
1: REST al server di autenticazione, risponde con un token
 ed ad ogni comando REST con il token e lo cambi ad ogni richiesta, in 
modo che il token sia valido solo per 1 richiesta
 
2: pass user/pwd ad ogni richiesta
 
 
-- 
Jeff

 autorizzata puo' fare quello che vuole?
 
Boh, è questo che non riesco a capire.
Non lo so nemmeno io. In questo preciso istante a me interessa fare il login.
In futuro potrei aver necessità di autorizzare una determinata operazione su un determinato servizio.
Esempio banale, il primo che mi viene in mente: utente X che opera sulla camera Y in un motore di prenotazione alberghiera.
 
Normalmente, le query che agiscono sui dati sono strutturate in modo tale da filtrare in base all'utente loggato, di conseguenza se il token/user/pass/quelchevuoi è XYZ, tutti gli applicativi sanno che XYZ può operare su tale camera e quindi automaticamente hanno l'autorizzazione.
 
Il problema si potrebbe presentare quando due applicativi devono condividere dati: 
 - app locale fa il login e prende il token
 - app locale interroga applicativo1 tramite il token
 - applicativo1 ha bisogno di alcuni dati che stanno su applicativo2 e lo interroga passandogli lo stesso token fornito da app locale
 - applicativo2 sa che sta operando per conto di utenteX (grazie al token) e restituisce i dati ad applicativo1
 
in pratica si passano il tocken a vicenda, verificando l'associazione token/utente ogni volta tramite il server token centralizzato.
 
Il problema è: se i dati stanno sia su applicativo1 che su applicativo2 (ovviamente dati differenti ma apparteneti allo stesso utente), come faccio ad associare lo stesso token utente a tali dati ? Ogni volta che viene generato il token dovrei andare a "rimappare" i vari applicativi tramite utente1<>token
 
Non so se mi sono spiegato.
 
Di recente ho bypassato in parte il problema tramite gli UUID.
L'utente viene creato sul server centrale e gli viene generato un UUID.
Il login viene fatto sul server centrale tramite user e password e viene generato un token, associato all'utente stesso.
 
Sui vari applicativi, non salvo mai "l'anagrafica" utente ma salvo solo i dati necessaria all'applicativo stesso, utilizzando l'UUID come identificativo.
In questo modo bypasso il problema di eventuali id autoincrement del db, un UUID sarà sempre un singolo utente in tutti gli applicativi.
 
I vari applicativi ottengono l'UUID interrogando il server centrale tramite token ed una richiesta REST di questo tipo
 
GET 
https://servercentrale/token/xyz123
 
{"uuid": "6789365f-1e39-4bbc-b4e8-e6d0bebaba04"}
 
Ma mi sembra un sistema un pochino macchinoso, non vorrei esistesse un qualcosa di analogo e magari ben collaudato (openid grossomodo fa la stessa cosa)
 
Ma sopratutto: se in futuro volessi aggiungere, tra i sistemi di autenticazione, veramente openid tramite ad esempio google ?
Con il mio meccanismo sarebbe impossibile, non avrei il token e di conseguenza la mappatura con l'UUID utente.
 

 Con il mio meccanismo sarebbe impossibile, non avrei il token e di conseguenza la mappatura con l'UUID utente.
 
Potrei quasi azzardare un sistema analogo a quello di github ed il suo oauth.
Tramite una autenticazione http base (user+pass) si ottiene un token oauth associato ad un dato utente. In questo modo il sistema di generazione token è "standard"
 
Il più resta capire come fare ad autenticare utenteX da dentro l'applicativoY ed eventualmente come poter utilizzare un server esterno per l'autenticazione (github, google, facebook, ...)

 Il più resta capire come fare ad autenticare utenteX da dentro l'applicativoY ed eventualmente come poter utilizzare un server esterno per l'autenticazione (github, google, facebook, ...)
 
Documentandomi maggiormente, la soluzione migliore sembra essere quella tramite oAuth. oAuth prevede anche una modalità di scambio user/pass in favore di un token, che sarebbe poi la modalità che serve a me per l'applicazione da linea di comando.
 
Allo stesso tempo, la stessa infrastruttura permetterebbe una autenticazione tramite webapp (sembra user/pass in cambio di un token).
 
Controllando nei sorgenti di varie implementazioni di oauth server, nessuna prevede una associazione token<>ip quindi lo stesso token sarebbe utilizzabile da più dispositivi. Nel mio caso sarebbe perfetto, avendo più Resource Server (ovvero il servizio che detiene i dati di cui voglio entrare in possesso), posso benissimo "rigirare" il token all'auth server ogni volta che viene interpellato un dato servizio.
 
Resta solo da capire come può, Applicativo10, riconoscere l'utente che è loggato, partendo da un semplice token, se tutta l'implementazione oAuth è su un server esterno. Nei vari esempi che ho trovato, il Resource Server è messo sullo stesso server del Auth Server, nel mio caso avrei 10 Resource Server ed 1 Auth Server.
 
Mi pare che le specifiche oauth non ne parlino e l'implementazione sia a carico del progettista.
Google ha aggiunto un endpoint apposito da richiamare per verificare un token e come risposta fornisce informazioni sul proprietario del token (l'utente loggato):
https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
 
 
mi sfugge però un punto fondamentale: che sicurezza può avere tutto il sistema se per impersonare un determinato utente, basta fornire un token?
User e pass sarebbero più complessi, sono 2 elementi da dover identificare anzichè uno solo.
Potrebbe andar bene se la verifica del token fosse fatta solo da parte di applicativi trusted (come le mie webapplication, che essendo mie, so per certo non essere manomesse), ma la stessa cosa lo dovrebbe fare l'applicativo che riceve l'autenticazione tramite CLI installata nel computer utente.
Chi impedisce ad un malintenzionato di forzare il sistema provando con token randomici tramite cli ?
 
Potrei, al limite, generare, lato server, un token codificato in AES anzichè casuale e decrittarlo sempre lato server verificandone il suo contenuto.

 
 
Quindi applicativo 1 e 2 sono due robe che stanno in remoto giusto?
Quello che vuoi fare e' fare richieste da APP1 verso APP2 per conto del 
CLIENT, giusto?
 
In questo caso di solito il client deve autorizzare APP1 a fare richieste 
a APP2 creando un token che viene salvato su APP1 e su APP2.
 
Quando CLIENT fa una richiesta a APP1, APP1 fa una chiamata ad APP2 con il 
token creato in precedenza, se per APP2 il token e' valido allora ok 
altrimenti nulla accade.
Una volta che in APP2 hai un token, puoi poi gestire eventualmente anche 
ACL in APP2
 
 
-- 
Jeff

 ACL in APP2
 
Ecco, appunto. (continuo a documentarmi quindi scopro cose nuove)
I token oAuth, sono, da specifiche, portabili. Le specifiche non impongono alcuna limitazione in merito, quindi io posso benissimo fare il login tramite cli (con il già citato sistema di scambio user/pass in favore di un token).
Tale token sarà valido in tutta l'infrastruttura oauth.
Sia APP1 che APP2 supporteranno la doppia autenticazione: 
 a) tramite token diretto, nel caso fosse stato in precedenza generato da qualche altra parte nella mia rete
 b) tramite scambio user/pass nel caso fosse una prima autenticazione.
 
In ambo i casi avrò come risultato un token, valido ovunque.
APPx riceve l'autenticazione tramite header HTTP, la rigira ad AUTHSRV il quale genera un token. APPx consegna il token al client, in questo caso la CLI, che se lo salva da qualche parte in locale (tanto scade dopo X ore)
Questo mi permette di fare una sorta di autenticazione "passante":
 CLI -> APP1 -> AUTHSRV
 
Ora, quando CLI deve accedere alla risorse, fornirà il token ad APP1 o APP2.
Nessuna delle due APP avrà il token (è gestito da AUTHSRV) quindi devo creare un sistema per validare il token in maniera passante.
Google fa un endpoint di convalida. Potrei fare la stessa cosa e quando APP1 o APP2 ricevono il token, interrogano AUTHSRV per convalidare il codice, il quale dira: "OK, tu sei UTENTE1234" oppure "KO"
 
Facendo così avrei un singolo token, valido per tutta la rete, generato da qualunque APP e centralizzato in un singolo server.
 
In futuro, se volessi, ad esempio, realizzare un applicativo Android, potrei benissimo riciclare lo stesso sistema ma senza scambio di token con user/pass.
Tramite l'applicativo android farò inserire ClientID ed SecretID i quali saranno manualmente generati dal cliente tramite un pannello web (l'implementazione standard di oauth che si vede in giro per la rete). 
Anche in questo caso avrò come risultato un token e quindi perfettamente utilizzabile da ovunque con il medesimo sistema di autenticazione che andrò a sviluppare (quello passante con convalida fatta tramite AUTHSRV)
 
Controindicazioni? La realizzazione non è nemmeno troppo complessa, dato che inizialmente mi servirà solo la modalità user/pass/token  quindi riciclando la Basic Auth http.