# Microsoft SSO Azure OAuth2
## Introduzione
E' stato registrata per gli sviluppatori un'applicazione di test sul portale Azure di Microsoft per poter testare il flusso di login tramite SSO. Il protocollo da utilizzare è OAuth2, di seguito lo schema:

### Processo di Autenticazione
L'intero processo di autenticazione deve essere fatto lato Frontend. Allego di seguito la procedura generale se si vuol seguire un approccio manuale. Viceversa si può pensare di utilizzare un driver di libreria frontend che permette di eseguire questi processi in automatico.
#### Ottenere il code
Al tasto Login nella pagina dell'applicazione frontend, l’utente deve essere re-indirizzato alla pagina di login di Microsoft.
L’indirizzo è il seguente:
```rest
GET https://login.microsoftonline.com/:tenantID/oauth2/v2.0/authorize?client_id={clientID}&response_type=code&redirect_uri={redirect_uri}&scope={scope}
```
dove:
- **_tenantID_**: parametro che ci da il cliente
- **_clientID_**: ID dell'applicazione registrata su Azure
- _**redirect_uri**_: Url di una pagina dell'applicazione frontend su cui si atterrerà dopo aver fatto la login su Microsoft.
- **_scope_**: determina a quale tipo di risorsa si vuol accedere (esempio User.Read, email)
- **response_type**: "code"
#### Ottenere il token
Dopo aver inserito l’username e password sul form di Microsoft, si verrà catapultati sul _**redirect_uri**_ definito nel precedente url. A questo indirizzo verrà aggiunto un query parameters chiamato _**code**_ che dovrà essere usato per chiamare il prossimo servizio al fine di ottenere l'_**access_token**_:
Il seguente indirizzo dovrà essere chiamato nell'onLoad della pagina del redirect_uri:
```rest
POST https://login.microsoftonline.com/:tenantID/oauth2/v2.0/token
```
il cui _body_ sarà (form-data o x-www-form-urlencoded):
- **tenantID**: parametro che ci da il cliente
- **clientID**: ID dell'applicazione registrata su Azure
- **redirect_uri**: deve essere lo STESSO di quello indicato in /authorize
- **grant_type**: "authorization_code"
- **code**: codice tornato nel redirect uri come query string
Il servizio tornerà una response del genere:
```json
{
"token_type": "Bearer",
"expires_in": "3599",
"ext_expires_in": "3599",
"expires_on": "1666801591",
"not_before": "1666797691",
"resource": "00000002-0000-0000-c000-000000000000",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSIsImtpZCI6IjJaUXBKM1VwYmpBWVhZR2FYRUpsOGxWMFRPSSJ9.eyJhdWQiOiIwMDAwMDAwMi0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8yZmYwNmEwMy0xYzI0LTQwZjUtOWQzYi04NTRkOTNhYWVkN2YvIiwiaWF0IjoxNjY2Nzk3NjkxLCJuYmYiOjE2NjY3OTc2OTEsImV4cCI6MTY2NjgwMTU5MSwiYWlvIjoiRTJaZ1lOQk1uT2ZFTGw0aXBOa1VjL3ZXOGYvUEFRPT0iLCJhcHBpZCI6IjgxMDFjNzQ2LTc1MTMtNDU5ZS05NDdlLWM3NDFmMDhlZjk3YSIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzJmZjA2YTAzLTFjMjQtNDBmNS05ZDNiLTg1NGQ5M2FhZWQ3Zi8iLCJvaWQiOiIzZWEwYjA2Ny1hZGE1LTQxZTItOWY0YS01MTJjNjUwZWVjYmUiLCJyaCI6IjAuQVY4QUEycndMeVFjOVVDZE80Vk5rNnJ0ZndJQUFBQUFBQUFBd0FBQUFBQUFBQUJmQUFBLiIsInN1YiI6IjNlYTBiMDY3LWFkYTUtNDFlMi05ZjRhLTUxMmM2NTBlZWNiZSIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6IjJmZjA2YTAzLTFjMjQtNDBmNS05ZDNiLTg1NGQ5M2FhZWQ3ZiIsInV0aSI6ImVGWklyeE83Ymt1SklTdVhtYk1TQUEiLCJ2ZXIiOiIxLjAifQ.mVQIZQu0ofTqGye9YmMoHtn8sTcFD3MMEbkiKLQMSwhPXPS8w2AeTHos2c8wNVRQPNeXys2J2tZfDKXVU2iF_1kzM2to0lHyPqqB3VsYx6XjWFC1Fn4bqMo90xvjo-xooG85Lqt_u99cOni8pXy_kUicZw4jHj0zfdLZ0DBvkvK5AoGtUVHy-I8jdyarbUdDvECuJwTY-fFrTYZ6MdirU0vWCC9ySTLzXQ7uPyXZwOyLWMkh8xJjh2kx2IpVKeRk_RKb7vwcFKGKtaLNfsqvpcumuvuewMXLc9KzoeIlXVXKlppi3Agb_EQww3qCGLeyoch1La1CJa2TVZepFAXbXw"
}
```
L'access_token si **dovrà salvare** da qualche parte nel browser ed utilizzare per autenticare ogni chiamata verso il server API. (All'interno di un header Authorization: Bearer {Access Token})
#### Verificare l'access_token nel server API
Ogni volta che il frontend effettuerà una richiesta autenticata al server, dovrà passare l'access_token ottenuto in precedenza.
Allego di seguito un template di un middleware utilizzato per il processo di verifica di questo tipo di token (JWK). In sintesi, il token essendo stato firmato da una chiave privata dall'identity provider (AZURE), per poterlo validare è necessario verificarlo con la sua chiave pubblica che otterremo dal servizio:
```rest
GET https://login.microsoftonline.com/[tenantID]/discovery/v2.0/keys
```
#### Middleware verifica token
```typescript
import type { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";
import ExpiredTokenException from "App/Exceptions/ExpiredTokenException";
import InvalidTokenException from "App/Exceptions/InvalidTokenException";
import CacheHelper from "App/Helpers/Cache";
import ApiHelper from "App/Helpers/RequestHttp";
import { TokenDecoded } from "App/Interfaces/JwtPayload";
import authConfig from "Config/auth";
import jwt from "jsonwebtoken";
import jose from "node-jose";
export default class Auth {
public async handle(
{ request }: HttpContextContract,
next: () => Promise<void>
) {
const authorizationHeader = request.header("Authorization");
if (!authorizationHeader) {
throw new InvalidTokenException("E_MISSING_AUTHORIZATION");
}
const { header, payload, token } = await this.decodeToken(
authorizationHeader
);
if (!payload.aud || payload.aud !== Env.get('AZURE_CLIENT_ID')) {
throw new InvalidTokenException()
}
await this.checkExpiration(payload);
try {
await this.checkSignature(header, token);
} catch (e) {
// If the token check fails it might be the fault of the stored and expired keys,
// let's try again by forcing a key update.
await this.checkSignature(header, token, true);
}
await next();
}
private async decodeToken(token: string): Promise<TokenDecoded> {
token = token.replace("Bearer ", "");
let decodeResult;
try {
decodeResult = jwt.decode(token, { complete: true });
} catch (e) {
throw new InvalidTokenException();
}
if (!decodeResult) {
throw new InvalidTokenException();
}
return {
header: decodeResult.header,
payload: decodeResult.payload,
token,
};
}
private async checkExpiration(payload) {
if (Date.now() >= payload.exp * 1000) {
throw new ExpiredTokenException();
}
}
private async checkSignature(
header,
token: string,
forceRefreshPublicKey = false
) {
if (!header.kid) {
throw new InvalidTokenException("E_MISSING_TOKEN_KID");
}
const publicKeys = await this.getPublicKey(forceRefreshPublicKey);
const keystore = await jose.JWK.asKeyStore(publicKeys);
const key = keystore.get(header.kid);
try {
await jose.JWS.createVerify(key).verify(token);
} catch (e) {
throw new InvalidTokenException();
}
}
private async getPublicKey(force = false): Promise<string> {
if (!CacheHelper.has(authConfig.publicKey.CACHE_KEY) || force) {
const keysValue = await ApiHelper({
method: "GET",
url: authConfig.publicKey.URL,
});
CacheHelper.set(authConfig.publicKey.CACHE_KEY, keysValue);
}
return CacheHelper.get(authConfig.publicKey.CACHE_KEY) as string;
}
}
```
## Credenziali
| PARAMETRO URL | |
| ------------- | ------------------------------------------------ |
| TENANT ID | 1bc5307f-4f0e-454c-bf1a-c988cd93a253 |
| CLIENT ID | api://c77bfb65-e488-4384-82c4-822f21bb960a |
| REDIRECT_URI | http://localhost:3000/auth/kering/callback |
| SCOPE | api://c77bfb65-e488-4384-82c4-822f21bb960a/email |
| CREDENZIALI MICROSOFT LOGIN | |
| --------------------------- | ------------------------------------- |
| USERNAME | amquser@azurevidiemme.onmicrosoft.com |
| PASSWORD | Fuyo0430! |