In response to a question from @MuqeetKhan regarding the use of authentication with an httpclient request.
Firstly, my motivation for using DI and factory was to allow me to easily extend my application to different and multiple APIs and have easy access to it throughout my code. His template, I hope, will be able to reuse it.
In the case of my GetUserPicture controller described in the original question above, I really removed authentication for reasons of simplicity. Honestly, however, I still doubt that I need it there just to get the image from the image server. In any case, in other controllers I really need this, so ...
Ive implemented Identityserver4 as my authentication server. This gives me authentication over ASP Identity. For authorization (using roles in this case), I implemented the IClaimsTransformer project in MVC and API projects (you can read more about this here in How to put Identity Server Server roles in Identityserver4 Identity Identifier ).
Now, when I log in to my controller, I have an authenticated and authorized user for whom I can get an access token. I use this token to call my api, which, of course, calls the same instance of the identity server to authenticate the user.
The final step is to let my API check if the user is allowed to call the requested api controller. In the API request pipeline using IClaimsTransformer, as explained earlier, I get the callerβs authorization and add it to the incoming requests. Please note that in case of calling MVC and API, I thus get authorization 2 times; once in the MVC request pipeline and once in the API request pipeline.
Using this setting, I can use my HttpClientsFactory with authorization and authentication.
On most of the security that I skip, there are HTTPS of course. Hope I can somehow add it to my factory. I will update it as soon as I implemented it.
As always, any suggestions are welcome.
Below is an example where I upload an image to a Picture using authentication (the user must be logged in and have the admin role).
My MVC controller calling "UploadUserPicture:
[Authorize(Roles = "Admin")] [HttpPost] public async Task<ActionResult> UploadUserPicture() { // collect name image server var imageServer = _optionsAccessor.Value.ImageServer; // collect image in Request Form from Slim Image Cropper plugin var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"]; // Collect access token to be able to call API var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); // prepare api call to update image on imageserver and update database var client = _httpClientsFactory.Client("imageServer"); client.DefaultRequestHeaders.Accept.Clear(); client.SetBearerToken(accessToken); var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("image", json[0]) }); HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content); if (response.StatusCode != HttpStatusCode.OK) { return StatusCode((int)HttpStatusCode.InternalServerError); } return StatusCode((int)HttpStatusCode.OK); }
API loading user load
[Authorize(Roles = "Admin")] [HttpPost] public ActionResult UploadUserPicture(String image) { dynamic jsonDe = JsonConvert.DeserializeObject(image); if (jsonDe == null) { return new StatusCodeResult((int)HttpStatusCode.NotModified); } // create filname for user picture string userId = jsonDe.meta.userid; string userHash = Hashing.GetHashString(userId); string fileName = "User" + userHash + ".jpg"; // create a new version number string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss"); // get the image bytes and create a memory stream var imagebase64 = jsonDe.output.image; var cleanBase64 = Regex.Replace(imagebase64.ToString(), @"^data:image/\w+;base64,", ""); var bytes = Convert.FromBase64String(cleanBase64); var memoryStream = new MemoryStream(bytes); // save the image to the folder var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName); FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write); try { memoryStream.WriteTo(file); } catch (Exception ex) { _logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath); return new StatusCodeResult((int)HttpStatusCode.NotModified); } memoryStream.Dispose(); file.Dispose(); memoryStream = null; file = null; // update database with latest filename and version bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result; if (!isUpdatedInDatabase) { return new StatusCodeResult((int)HttpStatusCode.NotModified); } return new StatusCodeResult((int)HttpStatusCode.OK); }