Autenticazione tramite OAuth Bearer Token in ASP.NET Web API

di Marco De Sanctis, in ASP.NET Web API,

Quando vogliamo garantire l'accesso sicuro a un servizio Web API, abbiamo diverse opzioni: la più semplice è sicuramente quella basata su cookie, anche se non è utilizzabile in tutti gli scenari.

Se per esempio vogliamo esporre i nostri servizi tramite CORS a siti di terze parti, o magari ad applicazioni non necessariamente web, un'autenticazione basata su OAuth Bearer Token è sicuramente la scelta più sensata. Il template di default di ASP.NET Web API è già configurato per sfruttare questo sistema, anche se il suo utilizzo non è esattamente immediato. Se creiamo un nuovo progetto e diamo un'occhiata al file Startup.Auth.cs, vedremo infatti il seguente codice:

public void ConfigureAuth(IAppBuilder app)
{
  // .. altro codice qui ..

  // Configure the application for OAuth based flow
  PublicClientId = "self";
  OAuthOptions = new OAuthAuthorizationServerOptions
  {
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
    // In production mode set AllowInsecureHttp = false
    AllowInsecureHttp = true
  };

  // Enable the application to use bearer tokens to authenticate users
  app.UseOAuthBearerTokens(OAuthOptions);
}

Soffermiamoci in particolare sull'ultima riga, che, come possiamo immediatamente notare, attiva la security basata su Bearer Token nella pipeline di OWIN, sfruttando una serie di opzioni definite in precedenza. Tra queste, una in particolare, TokenEndpointPath, definisce l'URL che dobbiamo invocare per recuperare un token valido. Il Provider utilizzato, invece, è ApplicationOAuthProvider, ossia una classe contenuta nel nostro progetto tramite cui possiamo personalizzare la logica di rilascio del token e dei claim in esso contenuti.

Prima di poter autenticarci, però, dobbiamo disporre di un utente. Il template di default sfrutta ASP.NET Identity per la gestione utenti, ed espone un metodo Register, all'interno di AccountController, che possiamo invocare in POST per creare un nuovo account utente:

POST /api/account/register HTTP/1.1
Host: localhost:15986
Content-Type: application/json
Cache-Control: no-cache

{
  "email":"test@test.com",
  "password":"Password123!",
  "confirmPassword":"Password123!"
}

Il body di questa richiesta è, nel nostro progetto, definito dalla classe RegisterBindingModel che viene accettata dal metodo Register. Se dobbiamo personalizzare qualcosa, per esempio usando uno username al posto della email, questo è il punto.

Ora che abbiamo a disposizione il nostro utente, possiamo invocare l'endpoint /Token, nuovamente in POST, passando le credenziali, per ottenere un token in risposta.

POST /token HTTP/1.1
Host: localhost:15986
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache

grant_type=password&username=test%40test.com&password=Password123!

Alcuni aspetti da notare in questa richiesta:

  • il Content-Type è settato a application/x-www-form-urlencoded; questa è una specifica del protocollo OAuth.
  • il campo grant_type è impostato a password, dato che stiamo effettuando l'autenticazione tramite username e password; esistono diverse altre tipologie, per esempio tramite RefreshToken o ClientCredential, che risultano utili in altri scenari;
  • username e password sono passati in chiaro: questo, ovviamente, costituisce un serio problema di sicurezza se non si usa HTTPS. Nelle opzioni impostate in precedenza, abbiamo settato AllowInsecureHttp a true, ma si tratta di una scelta da fare solo in fase di sviluppo e debug.

Se le credenziali sono corrette, il framework risponderà con un token, in maniera simile alla seguente, dandoci informazioni relative allo username e alla scadenza:

{
  "access_token": "h6mYQ7o97cr...",
  "token_type": "bearer",
  "expires_in": 1209599,
  "userName": "test@test.com",
  ".issued": "Sun, 14 Feb 2016 00:52:01 GMT",
  ".expires": "Sun, 28 Feb 2016 00:52:01 GMT"
}

A questo punto, possiamo effettuare una qualsiasi chiamata a uno degli endpoint dell'applicazione, per esempio a ValuesController in /api/values, passando il token nell'header con la chiave Authorization:

GET /api/values HTTP/1.1
Host: localhost:15986
Content-Type: application/json
Authorization: bearer h6mYQ7o97cr...
Cache-Control: no-cache

Se abbiamo fatto tutto correttamente, vedremo la risposta del nostro ValuesController nonostante sia protetto dall'attributo Authorize. Con un breakpoint sulla action, possiamo anche notare che la proprietà User del controller è correttamente valorizzata con i dati relativi all'utente corrente, unitamente con i claim che abbiamo rilasciato.

A questo punto potremmo chiederci dove viene implementata la logica di validazione e assegnazione dei claim all'utente. La risposta è la classe ApplicationOAuthProvider, che possiamo personalizzare per controllare aspetti quali il set dei claim restituiti o la scadenza del token stesso.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi