# API REST

Disseny i implementació de serveis web API, de tipus restful

# Conceptes

# Que és una API?

<span style="white-space: pre-wrap;">Una </span>****Aplication Program Interface (API)****<span style="white-space: pre-wrap;"> defineix les regles que han de complir-se que comunicar-se diferents sistemes software. Els desenvolupadors exposen o creen API per a que altres aplicacions puguin comunicar-se amb les seves aplicacions. Per exemple una aplicació del temps, podria exposar la previsió del temps segons el codi postal. De forma que quan rep un codi postal, cerca aquesta informació i retorna la previsió atmosfèrica per a aquella regió.</span>

![image.png](https://siensis.com/books/uploads/images/gallery/2025-05/scaled-1680-/xdFoPM98S7bVMPg1-image.png)****Fig. Esquema tradicional Client-Servidor****

Les API són com l'enllaç entre els clients i un recurs web. Els clients són usuaris que volen accedir a la informació des de la web. El client pot ser una persona o un software que utilitza aquesta API.

Els recursos són la informació que diferents aplicacions proporcionen als clients. Aquests recursos poden ser imatges, vídeos, text, números o qualsevol altre tipus d'informació. L'equip encarregat de lliurar aquest recurs seria el servidor, de forma que mitjançant les API poden compartir aquests recursos via serveis web, mantenint la seguretat, el control i l'autenticació.

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-05/scaled-1680-/8RMrpUAoOmHIWnU4-image.png)](https://siensis.com/books/uploads/images/gallery/2025-05/8RMrpUAoOmHIWnU4-image.png)

****Fig. Esquema crida a API****

# Que és REST?

****REST**** no és un protocol ni un estàndard, sinó un conjunt de límits de l'arquitectura.

Quan el client envia una sol·licitud a través d'una API RESTful, aquesta transfereix una representació de l'estat del recurs a qui l'hagi sol·licitat o a l'altre extrem. El lliurament d'aquesta informació es fa mitjançant HTTP en un d'aquests formats JSON, HTML, XLT, Python, PHP o text sense format. JSON actualment és el llenguatge més popular que permet que tant màquines com persones el puguin comprendre.

També és necessari tenir en consideració altres aspectes. Les capçaleres i els paràmetres també són importants en els mètodes HTTP d'una sol·licitud HTTP d'una API RESTful, ja que contindrà informació d'identificació important, metadades, autorització... Hi haurà capçaleres tant de sol·licitud com de resposta, però cadascun tindrà la seva funcionalitat.

Perquè una API es consideri RESTful, cal que compleixi els següents criteris:

- Arquitectura client-servidor composta per clients, servidors i recursos, amb la gestió de sol·licituds via HTTP
    - ****Ha d'estar orientada a recursos****<span style="white-space: pre-wrap;"> i </span>****utilitzarà**** <span style="white-space: pre-wrap;">les operacions estàndard dels </span>****verbs HTTP****
    - Farà servir ****l'URL com identificador únic****<span style="white-space: pre-wrap;"> dels recursos</span>
- <span style="white-space: pre-wrap;">Comunicació entre el client i el servidor sense estat, i això implica que no s'emmagatzema la informació del client entre les sol·licituds </span>****GET**** <span style="white-space: pre-wrap;">i que cadascuna d'elles és independent de la resta i està desconnectada de la resta. Això implica que </span>****no s'han d'utilitzar cookies ni variables de sessió****<span style="white-space: pre-wrap;">. La comunicació ha de ser </span>*****stateless*****
- <span style="white-space: pre-wrap;">Les dades han de poder emmagatzemar-se en </span>**cache** per permetre optimitzar les interaccions entre el client i el servidor
- Els elements han de tenir una interfície uniforme, perquè la informació es transfereixi de forma estandarditzada, això implica que s'han de complir les següents condicions:
    - Els recursos sol·licitats han de ser identificable e independents de les representacions enviades al client.
    - El client ha de poder manipular els recursos a través de la representació que rep, ja que contindrà prou informació per permetre-ho.
    - Els missatges, auto descriptius que s'envien al client han de contenir informació necessària per descriure com processar-la.
    - Ha de contenir hipertext o hipermèdia, de forma que quan el client accedeixi a algun recurs ha de poder utilitzar els hipervincles per cercar la resta d'accions que es necessiten.
- Un sistema de capes organitza en jerarquies invisibles per al client cadascun dels servidors (seguretat, equilibri de càrrega, etc.)

<span style="white-space: pre-wrap;">Si bé les API REST han de complir amb aquests paràmetres, també tenim la possibilitat d'emprar un protocol definit anteriorment anomenat </span>****SOAP**** (****protocol simple d'acces a objectes****<span style="white-space: pre-wrap;">), que té uns altres requisits específics, com missatgeria </span>****XML****, la seguretat i el compliment integrat de les operacions, que en general el fan més lent i pesat.

****REST****<span style="white-space: pre-wrap;">, en canvi, és un conjunt de pautes que es poden implementar segons sigui necessari. Per aquesta raó les API REST són </span>****més ràpides i lleugeres****, tenen una major capacitat d'ajustar-se i, per tant, resulten ideals per al IoT i el desenvolupament d'aplicacions mòbils.

# Com funciona?

La funció bàsica d'una API RESTful és la mateixa que navegar per internet. Quan es requereix un recurs, el client es posa en contacte amb el servidor mitjançant l'API. Els desenvolupadors de l'API expliquen com el client ha d'utilitzar-la mitjançant la documentació.

<span style="white-space: pre-wrap;">En general, els passos per fer una crida a una </span>****API REST****, són:

1. El client envia una sol·licitud al servidor. El client segueix la documentació de la API per donar format a la sol·licitud d'una forma que el servidor l'entengui.
2. El servidor autentica al client i confirma que aquest té el dret de fer aquesta sol·licitud
3. El servidor en rebre la sol·licitud la processa internament
4. Posteriorment retorna la resposta al client. Aquesta resposta conté la informació que diu al client si la sol·licitud es processa de forma correcta. La resposta també inclou qualsevol informació que el client hagi demanat.

Els detalls de la sol·licitud i la resposta de l'API REST varien en funció del desenvolupament i disseny.

# RESTful vs RESTless

****RESTful**** <span style="white-space: pre-wrap;">són totes aquelles API que compleixen amb els criteris REST, mentre que anomenarem </span>****RESTless**** a aquelles API que no acompleixen tots els criteris REST.

<span style="white-space: pre-wrap;">Per exemple, </span>

> ****una API que utilitzi el verb POST****<span style="white-space: pre-wrap;"> </span>****per totes les operacions****<span style="white-space: pre-wrap;"> no és una API RESTful, sinó una </span>****API RESTless****

# Beneficis de les API

<span style="white-space: pre-wrap;">Les </span>****API RESTful****<span style="white-space: pre-wrap;"> tenen els avantatges següents:</span>

#### ****Escalabilitat****

  
<span style="white-space: pre-wrap;">Els sistemes que implementen </span>****API REST****<span style="white-space: pre-wrap;"> poden escalar de forma eficient perquè REST optimitza les interaccions entre el client i el servidor. La tecnologia sense estat elimina la càrrega del servidor perquè no ha de retenir la informació de les sol·licituds anteriors del client. L'emmagatzematge en cache ben gestionat elimina de forma parcial o total algunes de les interaccions entre el client i el servidor. Totes aquestes característiques admeten l'escalabilitat, sense provocar colls d'ampolla en la comunicació que provoquin una reducció del rendiment.</span>

#### ****Flexibilitat****

  
<span style="white-space: pre-wrap;">Els serveis web </span>****RESTful**** admeten una separació total entre el client i el servidor. Simplifica i permet un desacoblament d'alguns dels components del servidor, de forma que cada part pot evolucionar de forma independent. Els canvis de la plataforma o la tecnologia en l'aplicació del servidor no afecta l'aplicació client. Per exemple, els desenvolupadors poden efectuar canvis a la capa de base de dades sense haver de reescriure la lògica de l'aplicació.

#### ****Independència****

  
<span style="white-space: pre-wrap;">Les </span>****API REST****<span style="white-space: pre-wrap;"> són independents de la tecnologia que utilitzen, és possible escriure les aplicacions de la banda client i de la banda servidor en diferents llenguatges de programació, sense afectar el disseny de l'API. També és possible canviar la tecnologia de qualsevol banda sense que aquest canvi afecti la comunicació.</span>

# API Specification?

Els principis de REST requereixen que la resposta del servidor contingui el següent:

##### ****HTTP Status****

El codi HTTP Status conté un codi d'estat de tres dígits que comunica si la sol·licitud s'ha processat correctament o ha donat un error. Per exemple, els codis 2XX indiquen que el procés és correcte, però els codis 4XX i 5X indiquen errors. Els codis 3XX indiquen que hi ha una redirecció de URL.

Per exemple,

- ****200****: resposta genèrica de processament correcte
- ****201****: resposta de processament correcte del mètode POST
- ****400****: resposta incorrecta que el servidor no pot processar
- ****404****: recurs no trobat

##### ****Cos de missatge****

El cos de la resposta conté la representació del recurs. El servidor seleccionarà un format de representació adient en funció del que continguin les capçaleres de la sol·licitud. Els clients ens poden demanar informació en formats XML o JSON o fins i tot en text sense format.

Per exemple, si un client sol·licita el nom i l'edat d'una persona anomenada Joan, el servidor podria retornar una representació JSON com la següent:

'{"name":"Joan", "age":30}'

##### ****Capçaleres****

La resposta també conté capçaleres o metadades sobre la resposta. Aquests afegeixen més context sobre la resposta o poden incloure informació addicional del servidor, com codificació, data, tipus de contingut, etc.

# Una crida API

<span style="white-space: pre-wrap;">L'especificació d'una API o API Spec, és la documentació que descriu el comportament d'una API, seria també com un contracte de l'API. La finalitat d'aquesta documentació és guiar al desenvolupador que va integrar la utilització de l'API al sistema. Hi ha diverses eines i estàndard creats específicament per descriure una API REST com són RAML, </span>[<span style="white-space: pre-wrap;">Swagger </span>](https://swagger.io/)<span style="white-space: pre-wrap;">i el estàndard </span>[OpenAPI](https://www.openapis.org/).

Els components que defineixen una API Spec són:

##### ****Verb HTTP****

Els verbs propis del protocol HTTP es varen definir per treballar les operacions més bàsiques sobre els recursos API. Els més utilitzats són:

- ****GET****: llista de recursos. Detall d'un sol recurs.
- ****POST****: creació d'un recurs
- ****PUT****: modificació total d'un recurs
- ****PATCH****: modificació parcial d'un recurs
- ****DELETE****<span style="white-space: pre-wrap;">: eliminació d'un recurs. En algunes ocasions es tracta d'un </span>**soft delete**, és a dir, no s'elimina definitivament un recurs si no únicament es marca com eliminat o desactivat. Si l'usuari no té l'autenticació adient, la sol·licitud fallarà.

##### ****URL orientada a recursos****

La definició de els URL, són els endpoint de l'API, és a dir, les entitats que tinguin coherència dins el context de l'API. Per exemple en una API d'un sistema que gestiona llibres, autors, editorials, col·leccions, etc. Les entitats que veuríem reflectides en els URL orientats a recurs serien:

- ****/api-llibres/v0/autors****<span style="white-space: pre-wrap;">: identifica els recursos anomenats autors </span><u>\[tots els autors\]</u>
- ****/api-llibres/v0/autors/{id-autor}****<span style="white-space: pre-wrap;">: identifica un recurs, autor </span><u>\[un autor concret\]</u>
- ****/api-llibres/v0/autors/{id-autor}/llibres****: identifica els llibres d'un autor en específic
- ****/api-llibres/v0/llibres****: identifica els recursos, llibres
- ****/api-llibres/v0/editorials****: identifica els recursos editorials
- ****/api-llibres/v0/editorials/{id-editorial}/llibres****: identifica els llibres d'una editorial

##### ****Capçaleres HTTP****

Les capçaleres HTTP de les sol·licituds són les metadades que s'intercanvien entre el client i el servidor. Per exemple, la capçalera d'una sol·licitud indica el format de la sol·licitud i la resposta, pot proporcionar informació sobre l'estat de la sol·licitud, etc...

****Dades o Body content****  
Les sol·licituds de la API REST poden incloure dades perquè els mètodes POST, PUT i altres funcionin de forma correcta

****Paràmetres****  
Les sol·licituds a la API RESTful poden incloure paràmetres que donin al servidor més detalls sobre el que han de fer, tipus d'ordenació, paràmetres d'una consulta...

# Autenticació API

<span style="white-space: pre-wrap;">Un servei web RESTful ha d'autenticar les sol·licituds abans de poder enviar una resposta. </span>

> L'autenticació és el procés per identificar una identitat

****Per exemple****, pot demostrar la seva identitat mostrant un DNI o llicència. De forma similar, els clients dels serveis RESTful han de demostrar la seva identitat al servidor per establir la confiança.

Les API RESTful tenen 4 mecanismes d'autenticació habitualment:

- ****Autenticació bàsica****  
    El client envia el nom i la contrasenya de l'usuari dins la capçalera de la sol·licitud codificada en base64, és una tècnica de codificació que converteix el parell en un conjunt de 64 caràcters per la seva transmissió segura.
- ****Autenticació del portador****  
    <span style="white-space: pre-wrap;">Aquest terme es refereix al procés de controlar l'accés del portador d'un token. El token acostuma a ser una cadena de caràcters xifrada que genera el servidor com a resposta a una sol·licitud d'inici de sessió. El client envia el token en totes les capçaleres de sol·licitud per accedir als recursos. Existeixen tokens de molts tipus, alguns s'envien xifrats, altres són un hash... entre molts d'altres tenim els tokens de tipus </span>[JWT (Javascript Web Token)](https://jwt.io)
- ****Claus API****  
    Les claus API són una altra opció per l'autenticació de les API REST. En aquest enfocament, el servidor assigna un valor únic generat a un client per primera vegada. Cada vegada que el client intenta accedir als recursos, utilitza una clau API única per la seva verificació. Les claus API són menys segures donat que cada client ha de transmetre la clau, cosa que ho torna més vulnerable al furt de xarxa.
- ****OAuth****  
    OAuth combina claus i tokens per a l'accés d'inici de sessió d'alta seguretat a qualsevol sistema. El servidor primer demana una clau i després sol·licita un token addicional per completar el procés d'autorització. Pot verificar el token en qualsevol moment i també al llarg del temps, amb una duració específica

<p class="callout info">[https://ca.wikipedia.org/wiki/JSON\_Web\_Token](https://ca.wikipedia.org/wiki/JSON_Web_Token)</p>

# CORS. Cross-origin resource sharing

<span style="white-space: pre-wrap;">CORS (Cross-origin resource sharing). És un mecanisme que permet sol·licitar recursos restringits, des d'una pàgina web d'un domini diferent del que ha servit el recurs. </span>

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/KJiRXPTXTDZSK4Tc-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/KJiRXPTXTDZSK4Tc-image.png)

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/Xwc7wS9m6W7m8soX-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/Xwc7wS9m6W7m8soX-image.png)

#### Exemple:

<span style="white-space: pre-wrap;">Suposa que visites el domini </span>****http://www.sample.com****<span style="white-space: pre-wrap;"> i aquest intenta obtenir un objecte JSON de </span>****http://api.sample.com****. Això generarà:

1. <span style="white-space: pre-wrap;"> Una petició GET amb una capçalera </span>****header**** <span style="white-space: pre-wrap;">dirigida a </span>****http://api.sample.com****<span style="white-space: pre-wrap;"> que serà:</span>
    - Origin: http://www.sample.com
2. El servidor api.sample.com respondrà amb el camp header Access-Control-Allow-Origin permetent únicament les peticions des del propi web
    - - A****ccess-Control-Allow-Origin****: http://www.sample.com
    - O podrà respondre amb la possibilitat que qualsevol domini s'hi pugui connectar
        - ****Access-Control-Allow-Origin****: \*
    - En cas que el servidor no respongui a la petició, es produirà un error i el recurs no serà tampoc accessible.

<span style="white-space: pre-wrap;">Addicionalment, el servidor </span>****http://api.sample.com****<span style="white-space: pre-wrap;"> podrà respondre quin tipus de VERBS (</span>*****METHODS*****) accepta, això ho farà responent amb la capçalera:

****Access-Control-Allow-Methods****: PUT, DELETE

També és possible indicar quins camps de capçalera es consideraran vàlids en qualsevol petició via el camp:

****Access-Control-Allow-Headers****: Origin, Authorization

Aquesta informació serà servida per http://api.sample.com si rep una petició via el mètode OPTIONS

# APIs en Codeigniter

# Codeigniter i les API

<table class="align-left" id="bkmrk-operationmethodcontr" style="box-sizing: border-box; border-collapse: collapse; color: rgb(106, 115, 123); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><colgroup><col></col><col></col><col></col><col></col><col></col><col></col></colgroup><tbody><tr style="box-sizing: border-box;"><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Operation****</th><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Method****</th><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Controller Route****</th><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Presenter Route****</th><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Controller Function****</th><th style="box-sizing: border-box; text-align: -webkit-match-parent;">****Presenter Function****</th></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">New

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;">photos/new

</td><td style="box-sizing: border-box;">photos/new

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">new()</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">new()</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Create

</td><td style="box-sizing: border-box;">POST

</td><td style="box-sizing: border-box;">photos

</td><td style="box-sizing: border-box;">photos

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">create()</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">create()</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Create (alias)

</td><td style="box-sizing: border-box;">POST

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">photos/create

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">create()</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">List

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;">photos

</td><td style="box-sizing: border-box;">photos

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">index()</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">index()</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Show

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;">photos/(:segment)

</td><td style="box-sizing: border-box;">photos/(:segment)

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">show($id = null)</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">show($id = null)</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Show (alias)

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">photos/show/(:segment)

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">show($id = null)</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Edit

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;">photos/(:segment)/edit

</td><td style="box-sizing: border-box;">photos/edit/(:segment)

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">edit($id = null)</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">edit($id = null)</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Update

</td><td style="box-sizing: border-box;">PUT/PATCH

</td><td style="box-sizing: border-box;">photos/(:segment)

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">update($id = null)</span>`

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Update (websafe)

</td><td style="box-sizing: border-box;">POST

</td><td style="box-sizing: border-box;">photos/(:segment)

</td><td style="box-sizing: border-box;">photos/update/(:segment)

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">update($id = null)</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">update($id = null)</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Remove

</td><td style="box-sizing: border-box;">GET

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">photos/remove/(:segment)

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">remove($id = null)</span>`

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Delete

</td><td style="box-sizing: border-box;">DELETE

</td><td style="box-sizing: border-box;">photos/(:segment)

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">delete($id = null)</span>`

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box;">Delete (websafe)

</td><td style="box-sizing: border-box;">POST

</td><td style="box-sizing: border-box;"><span style="white-space: pre-wrap;"> </span>

</td><td style="box-sizing: border-box;">photos/delete/(:segment)

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">delete($id = null)</span>`

</td><td style="box-sizing: border-box;">`<span class="editor-theme-code">delete($id = null)</span>`

</td></tr></tbody></table>

Codeigniter permet crear facilment RESTful APIs per als recursos del nostre projecte. Codeigniter disposa de les classes següents:

- ResourceController
- PresenterController

### API Request

Podem saber quin tipus de petició tenim, si es tracta d'una petició tipus API emprant:

```php
// Check for AJAX request.
if ($request->isAJAX()) {
    // ...
}
```

<span style="white-space: pre-wrap;">Això és possible si està establert el camp header X-Requested-With </span>

En aquest punt si les dades ens arriben via JSON, en aquest cas a la capçalera del request el camp CONTENT\_TYPE tindria el valor de “application/json”. Per obtenir els parametres que ens arriben a la API via body emprarem les funcions següents:

- ****$json = $request-&gt;getJSON();****
- ****$json = $request-&gt;getVar();****

En cas de voler obtenir un parametre concret de la petició podriem emprar:

- gerJsonVar('param');
- getVar('param');

****Exemple****

```php
// With a request body of:
/*
{
    "foo": "bar",
    "fizz": {
        "buzz": "baz"
    }
}
*/

$data = $request->getVar('foo');
// $data = "bar"
$data = $request->getVar('fizz.buzz');

// With the same request as above
$data = $request->getJsonVar('fizz');
// $data->buzz = "baz"

$data = $request->getJsonVar('fizz', true);
// $data = ["buzz" => "baz"]
```

### Controller response

<span style="color: rgb(29, 33, 37); background-color: rgb(255, 255, 255);">Podem accedir a la resposta que s'envia al navegador emprant el propi controlador, via:</span>

 ****$this-&gt;response-&gt;****

<span style="color: rgb(29, 33, 37); background-color: rgb(255, 255, 255);">Dins l'objecte resposta podrem establir tant els elements de capçalera si ens fan falta, com el cos de la mateixa.</span>

****$this-&gt;response-&gt;setStatusCode(404)-&gt;setBody('Nope. Not here.');****

****$this-&gt;response-&gt;setStatusCode(404, 'Nope. Not here.');****

També es possible enviar la resposta a client, en format JSON o XML.

<span style="white-space: pre-wrap;"> $data = \[</span>

<span style="white-space: pre-wrap;"> 'success' =&gt; true,</span>

<span style="white-space: pre-wrap;"> 'id' =&gt; 123,</span>

<span style="white-space: pre-wrap;"> \];</span>

 ****return $this-&gt;response-&gt;setJSON($data);****

<span style="white-space: pre-wrap;"> // or</span>

 ****return $this-&gt;response-&gt;setXML($data);****

Per establir altres valors de capçalera podrem emprar la funció setHeader()

****$this-&gt;response-&gt;setHeader('Location', 'http://example.com')****

 ****-&gt;setHeader('WWW-Authenticate', 'Negotiate');****

Podem afegir elements a una capçalera emprant appendHeader

****$this-&gt;response-&gt;setHeader('Cache-Control', 'no-cache')****

 ****-&gt;appendHeader('Cache-Control', 'must-revalidate');****

<span style="white-space: pre-wrap;">En cas necessari podem eliminar un item de capçalera via removeHeader </span>

****$this-&gt;response-&gt;removeHeader('Location');****

### API Response

<span style="color: rgb(29, 33, 37); background-color: rgb(255, 255, 255);">CI4 proporciona un objecte anomenat API Response</span><span style="white-space: pre-wrap;"> </span><span style="color: rgb(29, 33, 37); background-color: rgb(255, 255, 255);">trait</span><span style="white-space: pre-wrap;"> </span><span style="color: rgb(29, 33, 37); background-color: rgb(255, 255, 255);">que permet a qualsevol controlador retornar respostes simples amb els valors HTTP status segons ens interessi.</span>

```php
namespace App\Controllers;

use CodeIgniter\API\ResponseTrait;

class Users extends \CodeIgniter\Controller
{
    use ResponseTrait;

    public function index(){
        //...
        // Respond with 201 status code
        return $this->respondCreated();
    }
}
```

Aquest objecte ens permetria retornar els següents status code:

```php
// Generic response method
$this->respond($data, 200);
// Generic failure response
$this->fail($errors, 400);
// Item created response
$this->respondCreated($data);
// Item successfully deleted
$this->respondDeleted($data);
// Command executed by no response required
$this->respondNoContent($message);
// Client isn't authorized
$this->failUnauthorized($description);
// Forbidden action
$this->failForbidden($description);
// Resource Not Found
$this->failNotFound($description);
// Data did not validate
$this->failValidationError($description);
// Resource already exists
$this->failResourceExists($description);
// Resource previously deleted
$this->failResourceGone($description);
// Client made too many requests
$this->failTooManyRequests($description);
```

# Crear una API en Codeigniter

<span style="white-space: pre-wrap;">Per crear una API Restful primer serà necessari crear un controlador de codeigniter indicant que aquest és un controlador de tipus restful amb el paràmetre </span>****--restful****, així mateix per mantenir tots els controladors i moduls correctament etiquetats es molt recomanable que els arxius/classes tinguin com a suffix de quin tipus d'element es tracta (controller, model, migration...)

```
php spark make:controller ApiNoticies --suffix --restful
```

<details id="bkmrk-codi-generat%3C%3Fphp-na"><summary>Codi generat</summary>

```php
<?php

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;

class ApiNoticiesController extends ResourceController
{
    /**
     * Return an array of resource objects, themselves in array format.
     *
     * @return ResponseInterface
     */
    public function index()
    {
        //
    }

    /**
     * Return the properties of a resource object.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function show($id = null)
    {
        //
    }

    /**
     * Return a new resource object, with default properties.
     *
     * @return ResponseInterface
     */
    public function new()
    {
        //
    }

    /**
     * Create a new resource object, from "posted" parameters.
     *
     * @return ResponseInterface
     */
    public function create()
    {
        //
    }

    /**
     * Return the editable properties of a resource object.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function edit($id = null)
    {
        //
    }

    /**
     * Add or update a model resource, from "posted" properties.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function update($id = null)
    {
        //
    }

    /**
     * Delete the designated resource object from the model.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function delete($id = null)
    {
        //
    }
}
```

</details><span style="white-space: pre-wrap;">El codi que es genera es correspon a un controlador de tipus </span>****ResourceController**** amb les funcions:

- ****index****: Obtenir tots els elements
- ****show (id)****: Obtenir un element concret
- ****new:**** Crear un nou element buit
- ****create:**** Afegir un nou element
- ****edit (id):****<span style="white-space: pre-wrap;"> Obtenir un element per editar-lo</span>
- ****update (id):****<span style="white-space: pre-wrap;"> Desar un element despres editar-lo</span>
- ****delete (id):****<span style="white-space: pre-wrap;"> Eliminar un element</span>

<details id="bkmrk-exemple-%2F%2A%2A-%2A-return"><summary>Exemple</summary>

```php
/**
     * Return an array of resource objects, themselves in array format
     *
     * @return mixed
     */
    public function index()
    {
        $model = new NewsModel();
        $response = [
            'status' => 200,
            "error" => false,
            'messages' => 'News list',
            'data' => $model->findAll()
        ];
        return $this->respond($response);
    }

    /**
     * Return the properties of a resource object
     *
     * @return mixed
     */
    public function show($id = null)
    {
        $model = new NewsModel();
        $data = $model->find($id);
        if (!empty($data)) {
            $response = [
                'status' => 200,
                "error" => false,
                'messages' => 'Single news data',
                'data' => $data
            ];
        } else {
            $response = [
                'status' => 500,
                "error" => true,
                'messages' => 'No news found',
                'data' => []
            ];
        }
        return $this->respond($response);
    }


    /**
     * Create a new resource object, from "posted" parameters
     *
     * @return mixed
     */
    public function create()
    {
        $rules = [
            'title' => 'required|min_length[3]|max_length[128]',
            'body' => 'required',
        ];
        $messages = [
            "body" => [
                "required" => "Cos de noticia necessari"
            ],
            "title" => [
                "required" => "Titol noticia necessari",
                "min_length" => "Titol massa curt",
                "max_length" => "Titol massa llarg"
            ],
        ];
        if (!$this->validate($rules, $messages)) {
            $response = [
                'status' => 500,
                'error' => true,
                'message' => $this->validator->getErrors(),
                'data' => []
            ];
        } else {
            $model = new NewsModel();
            $title = $this->request->getVar('title');
            $slug = url_title($title);
            $body = $this->request->getVar('body');

            $newId = $model->addNoticia($title, $slug, $body);
            $response = [
                'status' => 200,
                'error' => false,
                'message' => 'News added successfully',
                'data' => [
                    'id' => $newId,
                    'title' => $title,
                    'slug' => $slug,
                    'body' => $body
                ]
            ];
        }
        return $this->respondCreated($response);
    }

    /**
     * Add or update a model resource, from "posted" properties
     *
     * @return mixed
     */
    public function update($id = null)
    {
        $rules = [
            'title' => 'required|min_length[3]|max_length[128]',
            'body' => 'required',
        ];
        $messages = [
            "body" => [
                "required" => "Cos de noticia necessari"
            ],
            "title" => [
                "required" => "Titol noticia necessari",
                "min_length" => "Titol massa curt",
                "max_length" => "Titol massa llarg"
            ],
        ];
        if (!$this->validate($rules, $messages)) {
            $response = [
                'status' => 500,
                'error' => true,
                'message' => $this->validator->getErrors(),
                'data' => []
            ];
        } else {
            $model = new NewsModel();
            if ($model->find($id)) {
                $title = $this->request->getVar('title');
                $slug = url_title($title);
                $body = $this->request->getVar('body');

                $model->updateNoticia($id, $title, $slug, $body);
                $response = [
                    'status' => 200,
                    'error' => false,
                    'message' => 'News updated successfully',
                    'data' => [
                        'id' => $id,
                        'title' => $title,
                        'slug' => $slug,
                        'body' => $body
                    ]
                ];
            } else {
                $response = [
                    'status' => 500,
                    "error" => true,
                    'messages' => 'No news found',
                    'data' => []
                ];
            }
        }
        return $this->respondUpdated($response);
    }

    /**
     * Delete the designated resource object from the model
     *
     * @return mixed
     */
    public function delete($id = null)
    {
        $model = new NewsModel();
        $data = $model->find($id);
        if (!empty($data)) {
            $model->where(['id' => $id])->delete();
            $response = [
                'status' => 200,
                "error" => false,
                'messages' => 'News deleted successfully',
                'data' => []
            ];
        } else {
            $response = [
                'status' => 500,
                "error" => true,
                'messages' => 'No news found',
                'data' => []
            ];
        }
        return $this->respondDeleted($response);
    }
```

</details><span style="white-space: pre-wrap;">Un cop generat el controlador, cal crear les rutes dins l'arxiu </span>****App/Config/Routes.php****<span style="white-space: pre-wrap;"> com qualsevol altra crida. Aquestes rutes es poden crear emprant </span>****routes-&gt;resources**** <span style="white-space: pre-wrap;">o </span>****routes-&gt;get/post/...****

```php
//***************************************************************************
// Agrupant les crides amb el nom de la funció list/add/show... 
// NOTA: No segueix el criteri RESTFUL
$routes->group("news", function($routes){
    $routes->get("list", "ApiNoticiesController::index");
    $routes->post("add", "ApiNoticiesController::create");
    $routes->get("show/(:num)", "ApiNoticiesController::show/$1");
    $routes->put("update/(:num)", "ApiNoticiesController::update/$1");
    $routes->delete("delete/(:num)", "ApiNoticiesController::delete/$1");
});
//***************************************************************************
// Crear les rutes  amb el nom del recurs 'news' i actuar segons el HTTP Method
$routes->get   ("news", "ApiNoticiesController::index");
$routes->post  ("news", "ApiNoticiesController::create");
$routes->get   ("news/(:num)", "ApiNoticiesController::show/$1");
$routes->put   ("news/(:num)", "ApiNoticiesController::update/$1");
$routes->delete("news/(:num)", "ApiNoticiesController::delete/$1");

//***************************************************************************
// Emprar el objecte/funcio resource que crea totes les rutes automaticament
$routes->resource('news', ['controller' => 'ApiNoticiesController']);

// Aquesta ruta equivaldria al següent:
$routes->get('news/new',             'ApiNoticiesController::new');
$routes->post('news',                'ApiNoticiesController::create');
$routes->get('news',                 'ApiNoticiesController::index');
$routes->get('news/(:segment)',      'ApiNoticiesController::show/$1');
$routes->get('news/(:segment)/edit', 'ApiNoticiesController::edit/$1');
$routes->put('news/(:segment)',      'ApiNoticiesController::update/$1');
$routes->patch('news/(:segment)',    'ApiNoticiesController::update/$1');
$routes->delete('news/(:segment)',   'ApiNoticiesController::delete/$1');


```

# Limitació APIs

## Throttler

Codeigniter conté una classe anomenada Throttler que proporciona prou eines per limitar l'activitat a la nostra API. Per exemple podem limitar el número d'intents en un període de temps, aquesta limitació és util per implementar limits de velocitat en APIs o restringir l'intent repetits en formularis, com els d'inici de sessió per així prevenir atacs de força bruta.

### Com funciona?

<span style="white-space: pre-wrap;">Throttler utilitza una versió simplificada d'algoritme </span>**Token Bucket**<span style="white-space: pre-wrap;">. Cada acció es tracta com un contenidor ("bucket") de forma que quan fas la crida a la funció </span>**check()**, has de definir:

- ****Nom del bucket****: identificador unic per l'acció
- ****Numero de tokens****: numero de vegades que es pot realitzar l'acció en un interval donat de temps
- ****Interval de temps****: el periode de temps durant el que es permet aquesta acció

<span style="white-space: pre-wrap;">Cada crida a </span>**check** suposa un token. Un cop s'esgoten els tokens, les accions adicions els bloquegen fins que el contenidor es carregui amb nou credit (**tokens**).

```php
// EXEMPLE throttler

$throttler = service('throttler');
if (! $throttler->check('la_meva_accio', 60, MINUTE)) {
    // Acció bloquejada
    // màxim 60 crides per minut
    return redirect()->back()->with('error', 'Massa crides. Intenta-ho més tard.');
}
```

Aquesta funcionalitat també es pot implementar com un servei i associar-lo. Pots consultar més informació i exemples a la documentació oficial de Codeigniter.

<p class="callout info">https://codeigniter.com/user\_guide/libraries/throttler.html</p>

# Securització APIs

# Mecanismes de securització

### Autenticació bàsica

És la forma més bàsica d'autenticació disponible per les aplicacions web, es va definir en la primera especificació del protocol HTTP. Sense ser un mecanisme elegant acompleix la seva funció. Aquest mecanisme no requereix la utilització de cookies, ni identificadors de sessió, ni pàgines de login.

Aquest procés fou dissenyat amb la finalitat que el navegador web pugui aportar les credencials basades en l'usuari i la contrasenya i que així li permetin autenticar-se davant d'un servei. Les dades s'envien codificades únicament en Base64. Aquest mecanisme és completament reversible i permet obtenir les dades que s'hi codifiquen sense cap dificultat.

<div drawio-diagram="57"><img src="https://siensis.com/books/uploads/images/drawio/2025-06/pwdUL8r4moDRXMHv-drawing-1-1749131989.png" alt=""/></div>

<p class="callout info">[Cyberchef. Base64](https://cyberchef.org/#recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=aG9sYSBtb24 "Cyberchef. Base64")</p>

Com funciona aquest protocol

<span style="white-space: pre-wrap;">Quan el servidor vol que el client s'autentiqui envia dins la capçalera de resposta a la petició el camp </span>****WWW-Authenticate****<span style="white-space: pre-wrap;"> per una autenticació bàsica.</span>

```
WWW-Authenticate: Basic realm="nmrs_m7VKmomQ2YM3:"
```

El client en el moment de rebre-ho, demana a l'usuari les credencials sol·licitades, per a construir la capçalera d'autorització que és una cadena del tipus "usuari:contrasenya", la cadena de caràcters resultant es codifica en RFC2045-MIME de Base64 sense la limitació de 76 caràcters que imposa la RFC. D'aquesta forma quedaria una cadena d'autenticació com la següent:

```
Authorization: Basic YWRtaW5pc3RyYWRvcjoxMjM0
```

<span style="white-space: pre-wrap;">Si utilitzem </span>[<span style="white-space: pre-wrap;">Cyberchef </span>](https://cyberchef.org/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=WVdSdGFXNXBjM1J5WVdSdmNqb3hNak0w "Decodificació del usuari i password")podem obtenir usuari i password fàcilment

<p class="callout info">[https://es.wikipedia.org/wiki/Autenticación\_de\_acceso\_bàsica](https://es.wikipedia.org/wiki/Autenticaci%C3%B3n_de_acceso_b%C3%A0sica "Wiki autenticació bàsica")</p>

### <span style="white-space: pre-wrap;">Autenticació del portador </span>

L'autenticació del portador consisteix en la comunicació mitjançant un token que s'envia en cada crida. Aquest element permet verificar la identitat o el rol de l'usuari a la part client.

L'usuari envia el nom d'usuari i la seva contrasenya i el servidor després de verificar-ho li retorna un token. A partir d'aquell moment aquest token és l'element que envia a cada petició que fa el client.

<div drawio-diagram="58"><img src="https://siensis.com/books/uploads/images/drawio/2025-06/QL64uW0vUVXQas1K-drawing-1-1749133549.png" alt=""/></div>

Els tokens generats poden ser de diferents tipus:

- Codi basat tipus UUID que s'emmagatzema en el servidor per saber qui l'ha generat i els privilegis que té aquest usuari.
- <span style="white-space: pre-wrap;">Token en format </span>[Tokens JWT](https://siensis.com/books/books/api-rest/page/tokens-jwt "Tokens JWT")

### Claus API

En alguns casos l'usuari final o client no serà l'encarregat d'utilitzar l'API sinó que són serveis externs que faran la funció de client i, per tant, seran l'origen de la crida a l'API. En aquestes ocasions a cada origen se li assigna una clau API per així fer el seguiment de com està essent usada l'API, i així evitar utilitzacions malicioses o abús de l'API.

En moltes ocasions aquesta clau API actua com identificador únic i utilitza també un testimoni o token secret per la seva validació. Igual que en els casos anteriors aquesta clau tindrà associats uns drets d'accés. Aquestes claus API poden estar basades en el sistema d'identificació universal unívoca o UUID per assegurar que cada usuari té una clau única.

<div drawio-diagram="62"><img src="https://siensis.com/books/uploads/images/drawio/2025-06/f9ojsbs4H7XGCYw9-drawing-1-1749135941.png" alt=""/></div>

****Exemple****: Accedim un web que ens mostra per pantalla un mapa de google Maps

### OAuth

Open Authorization és un estandard obert que permet fluxes d'autorització per a llocs web o aplicacions informàtiques. Aquest mecanisme permet a un usuari del lloc A (****proveïdor**** de servei) comparteix amb el lloc B (****consumidor****) sense compartir tota la seva identitat. Per als desenvolupadors de consumidors OAuth els permet interactuar amb dades protegides i publicar-les, per als proveïdors aquest mecanisme proporciona als usuaris un acces a les seves dades al mateix temps que protegeix les credencials del seu usuari. Aquest mecanisme està molt utilitzat per Google, Facebook, Microsoft, Twitter, entre altres, per permetre als usuari compartir informació sobre els comptes amb aplicacions de tercers o llocs web.

<div drawio-diagram="65"><img src="https://siensis.com/books/uploads/images/drawio/2025-06/583rMAjRmIWoQkTV-drawing-1-1749140759.png" alt=""/></div>

<div drawio-diagram="66"><img src="https://siensis.com/books/uploads/images/drawio/2025-06/oLzLiPsbku9SfeCV-drawing-1-1749140796.png" alt=""/></div>

# Tokens JWT

****JSON Web Token****<span style="white-space: pre-wrap;"> (JWT) és un estàndard obert per l'intercanvi de tokens d'autenticació en arquitectures client-servidor. Aquest tokens es troben en format JSON, en un entorn web, de forma segura i per verificar la identitat o el rol de l'usuari en la part client.</span>

Per exemple, si un client s'identifica com administrador en la seva interacció amb el servidor, aquest genera un token, que s'envia al client. En endavant, el client l'enviarà en totes les comunicacions per provar que efectivament té drets d'administrador. Els successius cops que el servidor rebi el token, mirarà la validesa del token o el repositori dels mateixos per comprovar quin rol ha de tractar en aquesta invocació.

L'estructura d'un JWT és molt específica, consisteix en 3 cadenes de caràcters codificades en Base64 i separades per un punt. Cadascuna d'aquestes cadenes deriva d'un JSON amb una llista d'atributs (propietats)

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/DPabU9pHHcR4veC3-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/DPabU9pHHcR4veC3-image.png)

### Format

<span style="white-space: pre-wrap;">Un token JWT és un element compacte, fins i tot permet la impressió dels atributs que la componen, tots els elements finalitzen amb una signatura per verificar la seva autenticitat. Els atributs o </span>**claims** són definicions o asseveracions sobre l'objecte a validar. Alguns d'aquests atributs, però formen part de les especificacions del mateix JWT, altres venen definits per l'usuari.

<table id="bkmrk-cap%C3%A7alera%7B%22alg%22%3A-%22hs"><colgroup><col style="width: 97px;"></col><col style="width: 275px;"></col><col></col></colgroup><tbody><tr><td style="vertical-align: middle;">****Capçalera****

</td><td style="vertical-align: middle;">{

"alg": "HS256",

"typ": "JWT"

}

</td><td>Identifica l'algorisme amb què ha estat generada la signatura

HS256 indica que aquest testimoni és signat usant HMAC-SHA256.

El més típic és usar els algorismes HMAC amb SHA-256 (HS256) i Signatura RSA amb SHA-256 (RS256).

JWA (JSON Web Algorithms) {{RFC|7518}} n'introdueix més tant per autenticació com xifrat.

</td></tr><tr><td style="vertical-align: middle;">****Contingut****

</td><td style="vertical-align: middle;">{

"usuari": "admin",

"iat": 1422779638

}

</td><td>Conté les informacions que el client presenta. L'especificació JWT defineix set camps estàndards que acostumen a ser trobats aquí. També n'hi ha de personalitzats, segons quin sigui el propòsit del testimoni.

En aquest exemple hi ha un clam estàndard, Issued At Time (iat) i un de personalitzat (identificació).

</td></tr><tr><td style="vertical-align: middle;">****Signatura****

</td><td style="vertical-align: middle;">HMAC\_SHA256(

secret,

base64urlEncoding(capçalera) + '.' +

base64urlEncoding(cos)  
)

</td><td>Valida el testimoni de forma segura. El càlcul consisteix a codificar la capçalera i el contingut, usant codificació RFC 4648 Base64url, i concatenant-los amb un punt al mig. El resultat és xifrat segons indica la capçalera. El Base64url Encoding és similar a Base64, però usa diferents caràcters no alfanumèrics i omet caràcters de completat (padding).

</td></tr></tbody></table>

Les tres parts són codificades separadament emprant Base64URL Enconding i tots els apartats concatenats per produir el JWT final.

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/K2YIKyhpewtHC5G4-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/K2YIKyhpewtHC5G4-image.png)

[![JWT.io](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/Ssis1CZrDQzzPaCX-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/Ssis1CZrDQzzPaCX-image.png)

### Camps habituals

- ****iss (*********issuer*********)****: Identifica el responsable que ha emès el JWT
- ****sub (*********subject*********)****: És una cadena de caràcters case-sensitive o URI que identifica de forma única a què es refereix aquest JWT
- ****aud (*********audience*********)****: Cadena de caràcters case-sensitive o URI unica que identifica els destinataris previstos per a aquest JWT
- ****exp (*********expiration*********)****: Un numero que representa una data/hora específica em format "segons desde epoch" com es defeneix en POSIX. Aquesta data estableix el instant de temps a partir del qual aquest token JWT es considera invalid
- ****nbf (*********not before*********)****: És l'oposat a exp. Es tracta d'un nombre que representa una data/hora en format "segons desdes epoch" com defineix POSIC, pero estableix a partir de quin instant de temps el token comença a ser valid
- ****iat (*********issued at*********)****: Es tracta d'un numero que representa una data/hora, en el mateix format que exp i nbf, que indica el moment en que aquest token s'ha emes
- ****jti (*********JWT ID*********)****: Aquest atribut representa un identificador unic per al JWT. S'utilitza per diferenciar tokens que puguin tenir similar contingut

<span style="white-space: pre-wrap;">Tots els atributs definits en les </span>[RFC7519](https://datatracker.ietf.org/doc/html/rfc7519 "RFC7519")<span style="white-space: pre-wrap;"> tenen un nom curt per aconseguir l'objectiu que estableix que els token JWT han de ser el més petits possible.</span>

# API RESTful. CI4 & JWT Auth

### Instal·lar PHP JWT Library

```bash
composer update
composer require firebase/php-jwt
```

<span style="white-space: pre-wrap;">La llibreria firebase/php-jwt codifica i descodifica tokens JWT en PHP segons les especificacions de la norma </span>**RFC 7519**

### Crear JWT API-CI Components

#### Arxiu configuració

```bash
php spark make:config Apijwt
php spark key:generate --show
```

Cal crear un arxiu de configuració on definirem el comportament bàsic de la llibreria que desenvoluparem per emprar JWT i ens mostrarem per pantalla una clau generada pel propi codeigniter que servirà per signar els tokens que generem amb aquesta llibreria.

En aquest fitxer de configuració podrem tenir la possibilitat de crear diferents perfils de JWT, tindrem una política que s'utilitzarà per defecte.

<details id="bkmrk-app%5Cconfig%5Capijwt.ph"><summary>App\\Config\\Apijwt.php</summary>

```php
/**
 * tokenSecret
 * Defines key to sign digitally the token JWT.
 * To generate in hex2bin use:
 *      php spark key:generate --show
 * To generate in base64 use:
 *      php spark key:generate --show --prefix base64
 *
 * You must store only the key, without algorithm prefix
 *
 * @var string
 ***********************************************************
 *
 * hash
 * Defines hash used to sign JWT token. The signing algorithm (alg field)
 *      Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
 *      'HS512', 'RS256', 'RS384', and 'RS512'
 *
 * @var string
 ***********************************************************
 *
 * authTimeout
 * Defines timeout for token JWT, exp field into token JWT
 *      Set null to disable timeout
 *
 * @var int
 ***********************************************************
 *
 * issuer
 * Issuer of the JWT
 *      Set null to ignore iss field from JWT token
 *
 * @var string
 ***********************************************************
 *
 * audience
 * Audience of the JWT
 *      Set null to ignore aud field from JWT token
 *
 * @var string
 ***********************************************************
 *
 * subject
 * Subject of the JWT
 *      Set null to ignore sub field from JWT token
 *
 * @var string
 ***********************************************************
 *
 * autoRenew
 * Renew automatically JWT token every request
 *
 * @var bool
 ***********************************************************
 *
 * oneTimeToken
 * JWT Token can be used only one time, toherwise is revoked
 *
 * @var bool
 ***********************************************************
 */
 public $policyName = "default"; // Defines default policy name, to use by Library/Filter

public $default = [
    'tokenSecret'  => 'b952674c72eff0e5e482b7525cde57a1fcdddc486a89658fd8985169cda341e9', //hex2bin.
    'hash'         => "HS256",
    'authTimeout'  => 30 * MINUTE,
    'issuer'       => "daw-company",
    'audience'     => "daw-company.user-db",
    'subject'      => "secure.jwt.v1.daw",
    'autoRenew'    => true,
    'oneTimeToken' => true,
    'renewTokenField' => 'refreshToken',
    'includePolicy' => true,
];

public $test = [
    'tokenSecret'  => 'b952674c72eff0e5e482b7525cde57a1fcdddc486a89658fd8985169cda341e9', //hex2bin.
    'hash'         => "HS256",
    'authTimeout'  => 24 * HOUR,
    'issuer'       => "test-company",
    'audience'     => "test-company.user-db",
    'subject'      => "secure.jwt.v1.test",
    'autoRenew'    => false,
    'oneTimeToken' => false,
    'renewTokenField' => 'refreshToken',
    'includePolicy' => true,
];

public function __construct($policy = null)
{
    parent::__construct();
    if ($policy != null)
        $this->policyName = $policy;
}

/**
 * Returns deafult configuration or configuration group 
 * 
 * @param  mixed $groupName Config groupname to get
 * @return object
 */
public function config($policy = null)
{
    if ($policy == null) $policy = $this->policyName;
    $props = get_object_vars($this);
    if (isset($props[$policy]) && is_array($props[$policy])) {
        $props[$policy]["policy"] = $policy;
        return (object) $props[$policy];
    } else {
        $props[$this->policyName]["policy"] = $this->policyName;
        return (object) $props[$this->policyName];
    }
}
```

</details>#### Taula Revoked

Aquest desenvolupament permet treballar amb tokens d'un sol ús, per aquest motiu cal disposar d'una taula per emmagatzemar els tokens que ja s'han fet servir fins que aquests caduquin i ja es puguin eliminar. Per això cal desenvolupar una migració.

```bash
php spark make:migration AddRevokeTokensTable
php spark migrate
```

<details id="bkmrk-app%5Cdatabase%5Cmigrati"><summary>App\\Database\\Migrations\\AddRevokeTokensTable.php</summary>

```php
public function up()
{
    $this->forge->addField([
        'tokenid'          => [
            'type'           => 'VARCHAR',
            'constraint'     => '36',
            'null'           => false,
        ],
        'subject'          => [
            'type'           => 'VARCHAR',
            'constraint'     => '128',
            'null'           => false,
        ],
        'expiration'          => [
            'type'           => 'INT',
            'constraint'     => 11,
            'null'           => false,
        ],
    ]);
    $this->forge->addPrimaryKey(['tokenid', 'subject']);
    $this->forge->createTable('tokens');
}

public function down()
{
    $this->forge->dropTable('tokens');
}
```

</details>#### Model JWT

Per interactuar amb la taula creada anteriorment serà necessari disposar d'un model que ens permeti, obtenir si un token està revocat, buidar la taula de tokens revocats, etc.

```bash
php spark make:model Tokens --suffix
```

<details id="bkmrk-app%5Cmodels%5Ctokensmod"><summary>App\\Models\\TokensModel.php</summary>

```php
protected $DBGroup          = 'default';
protected $table            = 'tokens';
protected $primaryKey       = ['tokenid', 'subject'];
protected $useAutoIncrement = true;
protected $insertID         = 0;
protected $returnType       = 'array';
protected $useSoftDeletes   = false;
protected $protectFields    = true;
protected $allowedFields    = ['tokenid', 'subject', 'expiration'];
// Dates
protected $useTimestamps = false;
protected $dateFormat    = 'datetime';
protected $createdField  = 'created_at';
protected $updatedField  = 'updated_at';
protected $deletedField  = 'deleted_at';
// Validation
protected $validationRules      = [];
protected $validationMessages   = [];
protected $skipValidation       = false;
protected $cleanValidationRules = true;
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert   = [];
protected $afterInsert    = [];
protected $beforeUpdate   = [];
protected $afterUpdate    = [];
protected $beforeFind     = [];
protected $afterFind      = [];
protected $beforeDelete   = [];
protected $afterDelete    = [];

public function get($token_data)
{
    $data = array(
        'tokenid' => $token_data->jti,
        'subject' => $token_data->sub??'subject.not.defined'
    );
    return $this->where($data)->first();
}

public function revoked($token_data)
{
    return $this->get($token_data) != null;
}

public function revoke($token_data)
{
    $data = array(
        'tokenid' => $token_data->jti,
        'subject' => $token_data->sub??'subject.not.defined',
        'expiration' => $token_data->exp
    );
    return $this->insert($data);
}
public function purge($time=null)
{
    if ($time===null) $time=time();
    $data = array('expiration <=' => $time);
    $query = $this->where($data)->delete();
   
    return $this->affectedRows();
}
```

</details>#### Helper JWT

Per treballar amb els tokens JWT ens ajudarem de diverses funcions que desenvoluparem a manera de Helper

<details id="bkmrk-app%5Chelpers%5Cjwt_help"><summary>App\\Helpers\\jwt\_helper.php</summary>

```php
<?php

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

/**
 * getToken
 * This function returns data from request JWT 
 * it examines request header to obtain Autorization: Bearer 
 * and decodes JWT to get payload.
 * 
 * If an error occurs on decoding, it throws a JWT Exception
 *
 * @param  mixed $cfgAPI    Config object
 * @param  mixed $request   Request object
 * @return object ['encoded' => string, "data" => object]
 */
if (!function_exists('getToken')) {

    function getToken($cfgAPI, $request)
    {
        $header = $request->header("Authorization");
        $token = null;
        if (!empty($header)) {
            if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
                $token = $matches[1];
            }
        }

        $token_data = JWT::decode($token, new Key($cfgAPI->tokenSecret, $cfgAPI->hash));

        $data = [
            "encoded" => $token,
            "data" => $token_data
        ];

        return $data;
    }
}

/**
 * renewTokenJWT
 * This function renews JWT token, with payload from a previous JWT. The
 * function renews jti (json token id), iat (issued at) and nbf (not before)
 * 
 * If an error occurs on encoding, it throws a JWT Exception
 *
 * @param  mixed $cfgAPI    Config policy object
 * @param  mixed $token_raw Token payload object
 * @return string (token)
 */
if (!function_exists('renewTokenJWT')) {

    function renewTokenJWT($cfgAPI, $token_raw)
    {
        $iat = time(); // current timestamp value

        if (isset($cfgAPI->authTimeout) && $cfgAPI->authTimeout != null)
            $token_raw->exp = $iat + $cfgAPI->authTimeout;

        $token_raw->iat = $iat;
        $token_raw->nbf = $iat;
        $token_raw->jti = App\Libraries\UUID::v4();

        $newtoken = JWT::encode((array)$token_raw, $cfgAPI->tokenSecret, $cfgAPI->hash);

        return $newtoken;
    }
}

/**
 * newTokenJWT
 * This function generates a new JWT token, it takes info from token policy
 * defined on config file. Adds payload and config items
 *
 * @param  mixed $cfgAPI    Config policy object
 * @param  mixed $data      Payload data, to add to JWT
 * @return string   Token generated
 */
if (!function_exists('newTokenJWT')) {

    function newTokenJWT($cfgAPI, $data)
    {
        $iat = time(); // current timestamp value

        $payload = array();

        if (isset($cfgAPI->authTimeout) && $cfgAPI->authTimeout != null)
            $payload["exp"] = $iat + $cfgAPI->authTimeout;

        if (isset($cfgAPI->issuer) && $cfgAPI->issuer != null)
            $payload["iss"] = $cfgAPI->issuer;

        if (isset($cfgAPI->audience) && $cfgAPI->audience != null)
            $payload["aud"] = $cfgAPI->audience;

        if (isset($cfgAPI->subject) && $cfgAPI->subject != null)
            $payload["sub"] = $cfgAPI->subject;

        $payload = array_merge(
            $payload,
            array(
                "nbf" => $iat,
                "iat" => $iat,                      // Issued at
                "jti" => App\Libraries\UUID::v4(),  // Json Token Id
            ),
            (array)$data
        );

        $token = JWT::encode($payload, $cfgAPI->tokenSecret, $cfgAPI->hash);

        return $token;
    }
}
```

</details>#### Filter JWT

Finalment, perquè els tokens JWT funcionin de forma automatitzada en les nostres API, crearem un filtre que s'encarregarà de revisar les peticions rebudes per validar el token JWT rebut quant a format, caducitats, etc. La qüestió de la seguretat o privilegis anirà a càrrec del controlador. Així mateix, aquest filtre s'encarregarà d'afegir en el retorn de la resposta, el nou token, si es tracta d'una API amb tokens d'un sol amb generació automàtica (segons arxiu de configuració).

```bash
php spark make:filter JWT --suffix
```

<details id="bkmrk-app%5Cfilters%5Cjwtfilte"><summary>App\\Filters\\JWTFilter.php</summary>

```php
<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;

use \Firebase\JWT\Key;
use \Firebase\JWT\JWT;


class JWTFilter implements FilterInterface
{

    /**
     * Do whatever processing this filter needs to do.
     * By default it should not return anything during
     * normal execution. However, when an abnormal state
     * is found, it should return an instance of
     * CodeIgniter\HTTP\Response. If it does, script
     * execution will end and that Response will be
     * sent back to the client, allowing for error pages,
     * redirects, etc.
     *
     * @param RequestInterface $request
     * @param array|null       $arguments
     *
     * @return mixed
     */
    public function before(RequestInterface $request, $arguments = null)
    {
        helper("jwt");
        $model = new \App\Models\TokensModel();

        if (isset($arguments))
            $cfgAPI = new \Config\APIJwt($arguments[0]);
        else
            $cfgAPI = new \Config\APIJwt();
      
        $header = $request->header("Authorization");
        $token = null;
        // extract the token from the header
        if (!empty($header)) {
            if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
                $token = $matches[1];
            }
        }
        // check if token is null or empty
        if (is_null($token) || empty($token)) {
            $response = service('response');
            $response->setBody('Access denied. Token required');
            $response->setStatusCode(401);
            return $response;
        } 
        try {
            $token_data = JWT::decode($token, new Key($cfgAPI->config()->tokenSecret, $cfgAPI->config()->hash));
            // check if token is defined with another policy and is not a valid token
            if (($token_data->sub ?? 'undefined') != ($cfgAPI->config()->subject ?? 'undefined')  ||
                ($token_data->aud ?? 'undefined') != ($cfgAPI->config()->audience ?? 'undefined') ||
                ($token_data->iss ?? 'undefined') != ($cfgAPI->config()->issuer ?? 'undefined')
            ) {
                $response = service('response');
                $response->setBody('Access denied. Wrong token params');
                $response->setStatusCode(401);
                return $response;
            }
            // check if token is revoked
            if ($model->revoked($token_data)) {
                $response = service('response');
                $response->setBody('Access denied. Token revoked');
                $response->setStatusCode(401);
                return $response;
            }
            // if oneTimeToken is enabled, revoke current token
            if ($cfgAPI->config()->oneTimeToken) {
                $model->revoke($token_data);
            }
            // store token data into request header to controller access
            $request->setHeader("token-data", json_encode($token_data));
            $request->setHeader("token-config", json_encode($cfgAPI->config()));
            $request->setHeader("jwt-policy", $cfgAPI->policyName);
        } catch (\Exception $ex) {
            $response = service('response');
            $response->setBody('Access denied. ' . $ex->getMessage());
            $response->setStatusCode(401);
            return $response;
        } finally {
            // clear expired tokens in revoked tokens table
            $model->purge();
        }
    }

    /**
     * Allows After filters to inspect and modify the response
     * object as needed. This method does not allow any way
     * to stop execution of other after filters, short of
     * throwing an Exception or Error.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param array|null        $arguments
     *
     * @return mixed
     * 
     * @link https://docs.microsoft.com/en-us/machine-learning-server/operationalize/how-to-manage-access-tokens
     */

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // ADD fields to api response, ONLY for StatusCode OK-200/CREATED-201/ACCEPTED-202
        if (
            $response->getStatusCode() == \CodeIgniter\HTTP\Response::HTTP_OK ||
            $response->getStatusCode() == \CodeIgniter\HTTP\Response::HTTP_CREATED ||
            $response->getStatusCode() == \CodeIgniter\HTTP\Response::HTTP_ACCEPTED
        ) {

            helper("jwt");

            if (isset($arguments))
                $cfgAPI = new \Config\APIJwt($arguments[0]);
            else
                $cfgAPI = new \Config\APIJwt();

            try {
                $values = json_decode($response->getBody());
                //check if $response->getBody() is a json string
                if (json_last_error() == JSON_ERROR_NONE) {
                    if ($cfgAPI->config()->oneTimeToken && $cfgAPI->config()->autoRenew) {

                        $token_data = json_decode($request->header("token-data")->getValue());

                        $newToken = renewTokenJWT($cfgAPI->config(), $token_data);

                        $renewTokenField = $cfgAPI->config()->renewTokenField;
                        $values->$renewTokenField = $newToken;
                    }
                    if ($cfgAPI->config()->includePolicy)
                        $values->policy = $cfgAPI->policyName;
                }
            } catch (\Exception $ex) {
                $response = service('response');
                $response->setBody('Access denied. After. ' . $ex->getMessage());
                $response->setStatusCode(401);
            } finally {
                if ($values !== null)
                    $response->setBody(json_encode($values));
            }
        }
    }
}
```

</details>#### Routes

Un cop tenim JWT configurat, actiu el filtre podrem utilitzar-lo com un filtre qualsevol a l'arxiu de routes

```php
/**
 * Call with default JWT policy
 * $routes->get("test", "ApiController::test",['filter'=>'jwt']);
 *
 * Call with custom JWT policy defined in APIJwt config file
 * $routes->get("test", "ApiController::test",['filter'=>'jwt:CONFIG_POLICY']);
 * $routes->get("test", "ApiController::test",['filter'=>'jwt:test']);
 *
 */
```

### Exemple: Utilització API amb JWT

#### Disable CSRF

Cal desactivar la necessitat dels codis CSRF que requeríem en els formularis, ja que la nostra API rebrà informació via POST/PUT/PATCH... per fer-ho caldrà modificar l'afectació del filtre CSRF i configurar les rutes a les quals no haurà d'afectar.

<details id="bkmrk-app%5Cconfig%5Cfilter"><summary>App\\Config\\Filter</summary>

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/JkcRtlDIIcunVtNV-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/JkcRtlDIIcunVtNV-image.png)

</details>#### API Securitzada

```bash
php spark make:controller Api --suffix --restful
```

<details id="bkmrk-app%5Ccontrollers%5Capic"><summary>App\\Controllers\\ApiController.php</summary>

```php
<?php

namespace App\Controllers;

use CodeIgniter\RESTful\ResourceController;
use App\Models\UsuarisDemoModel;
use Firebase\JWT\JWT;

class ApiController extends ResourceController
{
    public function login() {}

    public function test(){}
}
```

</details>#### Funció login

<details id="bkmrk-login-api.-genera-jw"><summary>Login API. Genera JWT Token</summary>

```php
/**
 * Login API to generate JWT token
 *
 */
public function login()
{
    helper("form");

    $rules = [
        'email' => 'required',
        'password' => 'required|min_length[4]'
    ];
    if (!$this->validate($rules)) return $this->fail($this->validator->getErrors());
    $model = new UsuarisDemoModel();
    $user = $model->getUserByMailOrUsername($this->request->getVar('email'));

    if (!$user) return $this->failNotFound('Email Not Found');

    $verify = password_verify($this->request->getVar('password'), $user['password']);

    if (!$verify) return $this->fail('Wrong Password');

    /****************** GENERATE TOKEN ********************/
    helper("jwt");
    $APIGroupConfig = "default";
    $cfgAPI = new \Config\APIJwt($APIGroupConfig);

    $data = array(
        "uid" => $user['id'],
        "name" => $user['name'],
        "email" => $user['email']
    );

    $token = newTokenJWT($cfgAPI->config(), $data);
    /****************** END TOKEN GENERATION **************/

    $response = [
        'status' => 200,
        'error' => false,
        'messages' => 'User logged In successfully',
        'token' => $token
    ];
    return $this->respondCreated($response);
}
```

</details>#### Funció test

<details id="bkmrk-test-jwt-api.-funci%C3%B3"><summary>Test JWT API. Funció de test JWT</summary>

```php
/**
 * API Sample call
 *
 */
public function test()
{        // Get current token payload as object
    $token_data = json_decode($this->request->header("token-data")->getValue());

    // Get current config for this controller request as object
    // $token_config = json_decode($this->request->header("token-config")->getValue());
   
    // Get JWT policy config
    // $policy_name = $this->request->header("jwt-policy")->getValue();

    // check if user has permission or token policy is ok
    // if user no authorized
    //      $this->fail("User no valid")

    $response = [
        'status' => 200,
        'error' => false,
        'messages' => 'Test function ok',
        'data' => [
            "data" => time(),
            "token-username" => $token_data->name,
            "token-email" => $token_data->email,
            'config'=>"la configuració del pacman"
        ]
    ];
    return $this->respond($response);
}
```

</details>#### Arxiu routes

```php
// All API functions with the same filter JWT policy
// $routes->group('api', ['filter' => 'jwt'], static function ($routes) 

// Every route with their filter JWT policy
$routes->group("api", function ($routes) {

    $routes->post("login", "ApiController::login");

    /**
     * Call with default JWT policy
     * $routes->get("test", "ApiController::test",['filter'=>'jwt']);
     *
     * Call with custom JWT policy defined in APIJwt config file
     * $routes->get("test", "ApiController::test",['filter'=>'jwt:CONFIG_POLICY']);
     * $routes->get("test", "ApiController::test",['filter'=>'jwt:test']);
     *
     */
    $routes->get("test", "ApiController::test", ['filter' => 'jwt']);
});
```

### Exemple Filtre CORS

En cas de requerir un filtre de CORS, aquest filtre està implementat dins de Codeigniter des de la versió 4.5 del Framework.

<p class="callout info">[Codeigniter 4. CORS Filter](https://codeigniter.com/user_guide/libraries/cors.html "Codeigniter 4. CORS Filter")</p>

Si es vol ajustar la seva implementació es tractaria de crear un nou filtre amb un contingut similar al següent:

```php
public function before(RequestInterface $request, $arguments = null)
{
  header("Access-Control-Allow-Origin: *");
  header("Access-Control-Allow-Headers: X-API-KEY, Origin,X-Requested-With, Content-Type, Accept, Access-Control-Requested-Method, Authorization");
  header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PATCH, PUT, DELETE");
  $method = $_SERVER['REQUEST_METHOD'];
  if ($method == "OPTIONS") {
      die();
  }
}
```

<span style="white-space: pre-wrap;">i un cop creat el filtre ajustant-ne el contingut a les necessitats del projecte, s'hauria d'activar com a filtre dins l'arxiu </span>****App\\Config\\Filters.php****

[![image.png](https://siensis.com/books/uploads/images/gallery/2025-06/scaled-1680-/vszYxs0KOQs1kNVH-image.png)](https://siensis.com/books/uploads/images/gallery/2025-06/vszYxs0KOQs1kNVH-image.png)

# Altres recursos

### Add Token automatically POSTMAN

Script to login URL

```javascript
pm.globals.set("token", pm.response.json().token);
```

<span style="white-space:pre-wrap;">Script to securized APIs. </span>****Pre-request script****

```javascript
pm.request.headers.add("Authorization: Bearer " + pm.globals.get("token"));
```

### <span style="white-space:pre-wrap;">Enllaços </span>

- [Postman. Software tool to check, build APIs](https://www.postman.com/ "Postman. Software tool to check, build APIs")
- [Automatic document an API](https://apidocjs.com/ "Automatic document an API")
- [Fake API sample](https://fakestoreapi.com/docs "Fake API sample")
- [JSON Web Tokens](https://jwt.io/ "JSON Web Tokens")
- [PHP Documentor](https://phpdoc.org/ "PHP Documentor")
- [RFC JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 "RFC JSON Web Tokens")<span style="white-space:pre-wrap;"></span>

### PDFs

- [JWT Handbook](https://siensis.com/books/attachments/1 "JWT Handbook")