|
using System; |
|
using System.Collections.Generic; |
|
using System.Dynamic; |
|
using System.Linq; |
|
using System.Net.Http; |
|
using System.Text; |
|
using System.Text.Json; |
|
using System.Threading.Tasks; |
|
using System.Web; |
|
using Microsoft.AspNetCore.Mvc; |
|
using testApp1.Models; |
|
|
|
namespace testApp1.Controllers |
|
{ |
|
// This is bare bones use of OAuth2 authorization for the sake of demonstration that is not coupled to any framework |
|
// Ideally each framework most probably has a library or internal implementation to ease the OAuth2 authorization |
|
// for asp.net core helpers regarding OAuth2 authorization this is a good example: https://www.red-gate.com/simple-talk/development/dotnet-development/oauth-2-0-with-github-in-asp-net-core/ |
|
public class NewsController : Controller |
|
{ |
|
private const string CookieName = "user_session"; |
|
private const string SitefinityAddress = "http://sf0:54507"; |
|
private const string TestAppAddress = "http://localhost:40982"; |
|
private const string ClientId = "testApp"; |
|
private const string Secret = "testsecret"; // do not hardcode secrets - use secure storage here, ex, get from environment variables |
|
private readonly string CallbackUrl = $"{TestAppAddress}/callback"; |
|
|
|
public IActionResult Index() |
|
{ |
|
string accessToken = null; |
|
if (this.Request.Cookies.ContainsKey(CookieName)) |
|
{ |
|
accessToken = this.Unprotect(this.Request.Cookies[CookieName]); |
|
} |
|
else |
|
{ |
|
// trigger authorization request to Sitefinity as described in https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 |
|
return this.Redirect($"{SitefinityAddress}/sitefinity/oauth/authorize?response_type=code&client_id={ClientId}&redirect_uri={HttpUtility.UrlEncode(CallbackUrl)}"); |
|
} |
|
|
|
if (accessToken != null) |
|
{ |
|
using (var client = GetClient()) |
|
{ |
|
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken); |
|
var response = client.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"{SitefinityAddress}/api/default/newsitems")).Result; |
|
response.EnsureSuccessStatusCode(); |
|
var responseBody = response.Content.ReadAsStringAsync().Result; |
|
var model = JsonSerializer.Deserialize<JsonElement>(responseBody).GetProperty("value"); |
|
return View(model); |
|
} |
|
} |
|
else |
|
{ |
|
throw new Exception("Access token cannot be null or empy."); |
|
} |
|
} |
|
|
|
[Route("callback")] |
|
public IActionResult Callback() |
|
{ |
|
var code = this.Request.Query["code"].First(); |
|
using (var client = GetClient()) |
|
{ |
|
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.GetEncoding("UTF-8").GetBytes($"{ClientId}:{Secret}"))); |
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{SitefinityAddress}/sitefinity/oauth/token") |
|
{ |
|
Content = new StringContent($"grant_type=authorization_code&code={code}&client_id={ClientId}&redirect_uri={HttpUtility.UrlEncode(CallbackUrl)}", Encoding.UTF8, "application/x-www-form-urlencoded"), |
|
}; |
|
var response = client.SendAsync(requestMessage).Result; |
|
response.EnsureSuccessStatusCode(); |
|
var responseBody = response.Content.ReadAsStringAsync().Result; |
|
var responseJson = JsonSerializer.Deserialize<JsonElement>(responseBody).TryGetProperty("access_token", out var accessTokenElement); |
|
var accessToken = accessTokenElement.GetString(); |
|
|
|
// also retrieve and persist refresh token server side to get new access token when it expires as to not make the user authenticate on Sitefinity again |
|
|
|
// persist access_token for the current browser session, in this case cookies are used |
|
this.Response.Cookies.Append(CookieName, this.Protect(accessToken), new Microsoft.AspNetCore.Http.CookieOptions() |
|
{ |
|
HttpOnly = true, |
|
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict, |
|
|
|
// Always use secure channels, for sake of simplicity in the example insecure channels are used |
|
// Secure = true |
|
}); |
|
} |
|
|
|
// get token and save to cookie |
|
return this.Redirect(TestAppAddress + "/news"); |
|
} |
|
|
|
private string Protect(string accessToken) |
|
{ |
|
// TODO: Cookies should use some form of protection - usually a symmetrical encryption. |
|
// It is recommended to use the built in data protection mechanisms of the framework |
|
return accessToken; |
|
} |
|
|
|
private HttpClient GetClient() |
|
{ |
|
var client = new HttpClient() |
|
{ |
|
BaseAddress = new Uri(SitefinityAddress), |
|
}; |
|
|
|
return client; |
|
} |
|
|
|
private string Unprotect(string value) |
|
{ |
|
// TODO: Cookies should use some form of protection - usually a symmetrical encryption. |
|
return value; |
|
} |
|
} |
|
} |