OAuth amb Android al Racó de la FIB

oauth-shine-300x298
OAuth amb Android al Racó de la FIB

El Racó de la FIB té una API accessible via OAuth. Utilitzar-la des d’una aplicació mòbil no és trivial i en aquest article intentarem veure com fer-ho des d’Android. Al mateix temps, és un exemple de com utilitzar aquest tipus d’autenticació en general per qualsevol altre API que funcioni amb OAuth.

Primer de tot, suposarem uns mínims coneixements de desenvolupament en Android.

El Racó de la FIB té una API accessible via OAuth. Utilitzar-la des d’una aplicació mòbil no és trivial i en aquest article intentarem veure com fer-ho des d’Android. Al mateix temps, és un exemple de com utilitzar aquest tipus d’autenticació en general per qualsevol altre API que funcioni amb OAuth.

Primer de tot, suposarem uns mínims coneixements de desenvolupament en Android. Concretament per seguir aquest exemple necessitarem

Un cop tinguem això, posem-nos mans a l’obra (o al codi, en aquest cas)

La coreografia

El flux d’OAuth, també conegut com “OAuth dance”, és el que ens permet que el nostre sistema autoritzi a una aplicació de tercers a accedir a la nostra informació. En el nostre cas, un usuari qualsevol del Racó donarà permís des del Racó per que una aplicació desenvolupada per nosaltres consulti seves dades personals.

El ball consta de 3 passes: request, authorization i access. Abans però haurem de fer les presentacions prèvies: el Racó ha de coneixer l’aplicació amb la que ballarà.

Coneixent a la nostra parella: registrar l’aplicació

Qualsevol aplicació que hagi d’accedir a informació del Racó ha d’estar prèviament registrada al Racó. La URL per registrar una aplicació nova és https://raco.fib.upc.edu/oauth/gestio-api/register.jsp

Un cop registrada l’aplicació, el Racó ens generarà dos valors: la clau i el secret. Aquest parell de valors aniran incorporats en cada petició que l’aplicació faci al Racó, de forma que qualsevol petició deixa rastre de l’aplicació que l’ha generat. Concretament, la clau es passa com un valor més dintre de la petició i el secret s’utilitza com a clau per generar una signatura digital de la petició. Com veurem, el fet d’utilitzar una llibreria OAuth ens abstraurà d’aquests detalls: ens hem de quedar amb la idea de que les peticions entre l’aplicació i el Racó van signades.

Primer pas: Request
En aquest pas, l’aplicació que estem desenvolupant demana un token al Racó, fent una petició signada. El Racó retorna un token a l’aplicació anomenat “request token”.

Segon pas: Authorization
La forma d’autoritzar el “request token” és presentar una pantalla de Racó a la qual li passem com a paràmetre el “request token”. Si l’usuari que utilitza l’aplicació l’accepta, el Racó es guardarà que el “request token” està validat i retornarà una redirecció cap a la nostra aplicació, el que s’anomena el Callback URL. Com que nosaltres no estem desenvolupant una aplicació web sinó una aplicació Android, aquesta URL no serà http:// sinó un tipus de URL diferent que mapejarem a la nostra aplicació Android utilitzant un Intent Filter. Resumint: des del Racó l’usuari valida el seu token i un cop validat es passa un altre cop el control a l’aplicació Android.

Tercer pas: Access
En aquests moments, tenim un “request token” validat i el que hem de fer és intercanviar-lo per un “access token”. Novament això implica una petició signada amb les claus de l’aplicació a una URL diferent. Aquesta petició ens retornarà un nou token, que serà el que la nostra aplicació es guardarà i que permetrà accedir a les dades de l’usuari que ha autoritzat. Potser ara us estareu preguntant.. per què ens hem complicant tant la vida per obtenir aquest token? No podem fer servir el que ja teníem? La resposta es que no, perquè el “request token” l’hem vist a la URL. En canvi, l’intercanvi per l'”access token” no implica cap interacció web amb l’usuari: és totalment entre l’aplicació i el Racó. La clau de la seguretat de OAuth es que es mantingui en secret.

Un cop fets aquests 3 passos, ja no cal tornar-los a fer. Mentre l’aplicació mantingui l’access token i aquest no caduqui, s’accediran a les dades de l’usuari sense que s’hagi de tornar a validar o autoritzar.

Pas a pas

Ara ja sabem quina és la nostra coreografia. Anem a fer-ho pas a pas perquè quedin clars els moviments, amb música (o codi, com preferiu).

Primer de tot, necessitarem una llibreria que ens ajudi a signar les peticions seguin el protocol OAuth. Utilitzarem la llibreria SignPost, molt simple d’utilitzar, i la inicialitzarem amb les dades bàsiques: les claus de l’aplicació i les URL de les diferents passes. SignPost ens proporciona un objecte Provider (que ens abstreu del Racó en aquest cas) i un Consumer (que representa a l’aplicació que estem desenvolupant). Així doncs, dintre de la nostra Activity tindrem els següents atributs:

    OAuthProvider provider = new DefaultOAuthProvider(
        "https://raco.fib.upc.edu/oauth/request_token",
        "https://raco.fib.upc.edu/oauth/access_token",
        "https://raco.fib.upc.edu/oauth/protected/authorize");    

    String callback="raco://raco";
    String key="4812b0b0-b1a9-11e2-9e96-0800200c9a66";
    String secret="54efb080-b1a9-11e2-9e96-0800200c9a66";
    DefaultOAuthConsumer consumer;

Per fer la petició del Request Token, ens trobem ràpidament amb una limitació d’Android: no podem posar codi que faci peticions a Internet dintre del thread principal de l’aplicació, així que farem les peticions dintre d’una AsyncTask. Un cop tinguem el token, els guardem (al localStorage per exemple) i obrim un navegador amb la URL de validació del token.

    class DemanaRequestTokenAsync extends AsyncTask<Void, Void, String> {
        
        @Override
        protected String doInBackground(Void... params) {
            String authURL=null;
            try {
                authURL = provider.retrieveRequestToken(consumer, callback);
                guardaTokens();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return authURL;
        }

        // Una vegada tinc el token obro ja el navegador. D'event en event...
        // Important! Abans de continguar he de guardar els tokens
        
        @Override
        protected void onPostExecute(String authURL) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(authURL)));
        }
    }

Quan validem el token al nostre navegador, ens redirigirà a la URL de callback, que serà de l’estil raco://raco. Com fem que això acabi executant una part de la nostra aplicació en comptes d’obrir una nova pàgina al navegador web? Molt fàcil: utilitzarem un Intent Filter que associarà aquest protocol a la nostra Activity

    <intent-filter>
        <action android:name="android.intent.action.VIEW"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
        <category android:name="android.intent.category.BROWSABLE"></category>
        <data android:host="raco" android:scheme="raco"></data>
    </intent-filter>
   
    public void onResume() {
        super.onResume();
        Uri uri = this.getIntent().getData();
        recuperaTokens();

        // Aixo es el cas en que tornem amb token. Si no, no fem res
        if (uri != null && uri.toString().startsWith(callback)) {
            new DemanaAccessTokenAsync().execute();
        }
    }

Com abans, el codi per demanar el “access token” s’ha d’executar de forma asíncrona, així que tornem a fer una AsyncTask similar a la que hem utilitzat per demanar el “request token”. La tasca demana i quan acaba guardem el “access token”. Aquí acaba el nostre ball: ja hem aconseguit de la nostra parella el que buscàvem.

    class DemanaAccessTokenAsync extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                provider.retrieveAccessToken(consumer,null);
                guardaTokens(true);
            } catch (Exception e) {
                Log.d("oauth","ha petat al access token");
            }
            return null;
        }

        // Una vegada tinc el access token i secret, ja puc fer feina...
        
        @Override
        protected void onPostExecute(Void result) {
            // Aqui el consumer s'ha actualitzat amb els 2 access tokens que ja li permeten fer les peticions
            // als nostres webservices. Aquests valor s'haurien de guardar i abans de tot fer un
            // consumer.setTokenWithSecret si existeixen.
        }
    }

S’ha acabat el ball. Ara comença l’interessant.

En aquests moments ja tenim el nostre preuat “access token”: la parella ja es coneix prou bé i pot començar la conversa. Com a exemple, demanarem la informació bàsica de l’usuari i obtindrem el nom complet. Podem fer moltes altres coses, com podem veure la pàgina de la API del Racó on hi ha les diferents dades que es poden obtenir. En el codi utilitzem l’objecte consumer que hem fet servir fins ara i que conté els “access token” necessaris per poder fer la petició signada. Pot ser que els tinguem perquè hem fet tot el ball o bé perquè ja els teníem d’una petició prèvia guardats al localStorage.

    public String getNom() {
            String json=demana("https://raco.fib.upc.edu/api-v1/info-personal.json";);
            JSONObject jObject = new JSONObject(json);
            return jObject.getString("nom"));
    }
        
    private String demana (String url) {
        StringBuffer aux=new StringBuffer();
        try {
        URL u = new URL(url);
        HttpURLConnection request = (HttpURLConnection) u.openConnection();
        consumer.sign(request);
        BufferedReader in = new BufferedReader(new InputStreamReader(
                request.getInputStream()));
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            aux.append(inputLine);
        }
        } catch (Exception e) {
            Log.i("oauth",e.getMessage());
        }
        return aux.toString();

    }

Conclusions

En aquest exemple hem vist com utilitzar des d’Android una API amb OAuth 1.0 amb la llibreria Signpost. Tot i que hem fet l’exemple amb el Racó, això és aplicable a qualsevol altre web que utilitzi OAuth 1.0. Moltes API, però, ja han migrat a OAuth 2.0, que entre d’altres coses simplifica la forma d’accedir.