I little unsure of my actions. I started my first web api application. So I wanna create api where is all functionality include authorization. And web and mobile applications which will work with my api. Something like that
But I discover problem on authorization step. I must handle many api's response variation like 401(unauthorized),200(OK) etc. I get response in Json format. And every response have own structure and its structure changes in differet cases. So that is the problem I can miss handle something and crash my app. How can I avoid it. May be I missunderstand something. I will be greateful for any help.
I create API on asp.net core framework.
Some response examples
OK 200
{"result":{"succeeded":true,"isLockedOut":false,"isNotAllowed":false,"requiresTwoFactor":false},"token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiI5YjkwZDFmZC1iMjQzLTRhMTEtYWQ3NS1iZWU0ZDJjNTJhNTEiLCJ1bmlxdWVfbmFtZSI6IkVlZm9zZmF0dXMxMzNAZ21haWwuY29tIiwibmJmIjoxNTkzOTU4MjM4LCJleHAiOjE1OTQwNDQ2MzgsImlhdCI6MTU5Mzk1ODIzOH0.AUjS7ocjp3Z_HuU1QqBPUG4NlNcRAihjOhbKBAC_6ecjjlZQM417M9KKGEk1uAr0yKFl9dcPNo04YPSKs-vJ7g"}
401 Unauthorized
{"type":"https://tools.ietf.org/html/rfc7235#section-3.1","title":"Unauthorized","status":401,"traceId":"|9ca7ae31-444c9220bfc1657b.1.88f5b6d2_"}
API's action for example
public async Task<IActionResult> Login(LoginModel loginModel)
{
if (ModelState.IsValid)
{
Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync
(loginModel.Email, loginModel.Password, loginModel.RememberMe, false);
if (result.Succeeded)
{
User user = await _userManager.FindByNameAsync(loginModel.Email);
ServiceResponse response = new ServiceResponse()
{
Result = result,
Token = CreateToken(user)
};
return Ok(response);
}
else
{
return Unauthorized();
}
}
return NoContent();
}
You should create a model for your responses and return all of your responses in that structure.
Example:
public class ResponseModel
{
public bool Succeeded { get; set; }
public string Message { get; set; }
public object Content { get; set; }
}
Ok Response structure :
{
"succeeded" : true,
"message" : "some-message",
"content" : {
"isLockedOut" : false,
"isNotAllowed" : false,
...
..
}
}
UnAuthorized Response structure :
{
"succeeded" : false,
"message" : "some-message",
"content" : {
"title" : "unauthorized",
"status" : "401",
...
...
}
}
Related
I am using ASP.Net Core 5 to create a web API. I use controllers like this
[Route("[controller]")]
[ApiController]
public class User : ControllerBase
{
...
public async Task<ActionResult<User>> GetUserByID(int id)
{
...
}
...
}
This works fine but means I keep creating defined typed classes for the data I am returning. I am interested in returning an anonymous type sometimes rather than a specific type, is this possible?
You can use IActionResult. For example:
[HttpGet, Route("getUserById/{id}")]
public async Task<IActionResult> GetUserByID(int id)
{
var data = await Something.GetUserAsync(id);
return Ok(new
{
thisIsAnonymous = true,
user = data
});
}
One thing you "could" do is to return a "string" type all the time by serializing the data - either into a JSON sting or XML. And then interpret accordingly on the client. However, you should ideally look at using the "ProducesResponseType" feature as well as several in-built helper methods to produce different responses based on different conditions - that way you can return different types based on different scenarios. See example below:
[HttpGet]
[ProducesResponseType(typeof(User), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(User), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(User), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<User>> GetUserByID(int id)
{
try
{
User model = await _userService.Get(id);
return Ok(model);
}
catch (ApiAccessException apiException)
{
ApiFailureDetail detail = new ApiFailureDetail { ApiError = apiException.ApiError, TechnicalMessage = apiException.TechnicalMessage, UserFriendlyMessage = apiException.UserFriendlyMessage };
//Serialize the exception
string errorOutput = JsonConvert.SerializeObject(detail);
return Unauthorized(errorOutput);
}
catch (ApiException apiException)
{
ApiFailureDetail detail = new ApiFailureDetail { ApiError = apiException.ApiError, TechnicalMessage = apiException.TechnicalMessage, UserFriendlyMessage = apiException.UserFriendlyMessage };
string errorOutput = JsonConvert.SerializeObject(detail);
return BadRequest(errorOutput);
}
catch (Exception e)
{
ApiFailureDetail detail = new ApiFailureDetail { ApiError = ApiError.InternalError, TechnicalMessage = e.Message, UserFriendlyMessage = "Internal unknown error." };
string errorOutput = JsonConvert.SerializeObject(detail);
return BadRequest(errorOutput);
}
}
I have an issue and I got no idea what to do, the issue is that on my C# WebApi app with swagger enabled.
i have a few apis but here is an example 1 of them.
[HttpPost]
[Route("/api/user/register")]
public UserSession Register(string email, string password, string confirm_password)
{
if (password != confirm_password)
{
return new UserSession()
{
Success = false,
Message = "Error Passwords don't match",
SessionKey = "",
};
}
// success code here
}
here is the angular API.
import axios from 'axios';
export class API {
private static base_api:string = "http://localhost:51019/";
static register(email:string, password:any, confirm_password:any) {
let url = this.base_api + "api/user/register";
let data = {
email: email,
password: password,
confirm_password: confirm_password,
};
const headers = {
'Content-Type': 'application/json'
};
let result = axios.post(url, data, {headers}).then(x=>{return x}).catch(x=>{return false;});
console.log(result);
}
}
even when I provide an email and/or a password, it's like the API isn't receiving the data?
to fix the cor issue i had i added this to the api controller
[HttpOptions]
[Route("/api/user/register")]
[Route("/api/user/login")]
[Route("/api/user/logout")]
public HttpResponseMessage Options()
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
return response;
}
if i access the api via the swagger ui via (http://localhost:51019/swagger/index.html)
then when i perform the api via the UI it works correctly.
TIA
Typically you'd create a model and set it as the body:
public sealed class RegisterModel
{
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
[JsonPropertyName("confirm_password")]
public string ConfirmPassword { get; set; }
}
[HttpPost, Route("/api/user/register")]
public UserSession Register([FromBody] RegisterModel register)
{
// ...
}
That will get it to work in all scenarios
I have been running into an issue with a specific call on CosmosDB from my API ( Net core 3.1 )
I am getting a error back :
"fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Text.Json.JsonException: A possible object cycle was detected which is not supported."
My User_Profile class is fairly simple :
using Newtonsoft.Json;
namespace API
{
public class User_Profile
{
[JsonProperty("id")]
public string Id { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
}
The Call on the database method :
public async Task<User_Profile> GetUserAsync (string id)
{
try
{
User_Profile foundUser = await ctner_Users.ReadItemAsync<User_Profile>(id, new PartitionKey(id));
return foundUser;
}
catch (Exception e)
{
_logger.LogWarning("AddUserAsync failed : " + e);
return null ;
}
}
This is the document shown in cosmos db explorer :
{
"id": "romain",
"Password": "Password",
"Role": "User",
"_rid": "VqgQAP51mcUCAAAAAAAAAA==",
"_self": "dbs/VqgQAA==/colls/VqgQAP51mcU=/docs/VqgQAP51mcUCAAAAAAAAAA==/",
"_etag": "\"4300e1b0-0000-0100-0000-5ee44ce00000\"",
"_attachments": "attachments/",
"_ts": 1592020192
}
That is the method in the controller :
[HttpPost("auth")]
public ActionResult<User_Profile> AuthUser ([FromBody] User_Profile authUser)
{
var user = _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
Been trying to debug / deglitch / found out where the issue is coming for quite some time, so i have removed a lot of the previous code i had, like password checking, model validation, ect ect...and tried to keep the very minimum to narrow it down, but even that gives me the same cycle error message.Knowing that my user proifile class only has 3 strings in it, i fail to see the cycle reference issue.
One thing i have tried was to to update json to last preview and add the reference handling , and set it to preserver : I instead got a very length error message, showing what seems liek to be every assembly versions possible.
To note : the other calls to the DB are working .. create user, or access other containers, read from them, ect... it really just this spefici call that gives me back that error, and i currently am at a complete loss on that one.Any help would be really appreciated.
EDIT : If i replace in the startup.cs with
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
Then the message that i get back when trying the same method on the controller is that very lengthy one ( i ve cut all the assembly section ) :
{
"stateMachine": {
"<>1__state": 0,
"<>t__builder": {},
"id": "romain",
"<>4__this": {}
},
//BIG CONTEXT SECTION HERE, ABOUT 0.5 MB of text...
"result": {
"id": "romain",
"password": "Password",
"role": "User"
},
"id": 1808,
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
EDIT 2 : If i try to retreive a List instead of a single item , it works :
[HttpPost("auth2")]
public async Task<ActionResult<List<User_Profile>>> GetUsers ([FromBody] User_Profile authUser)
{
List<User_Profile> list = await _db.GetUsersAsync(authUser.Id);
return Ok(list);
}
public async Task<List<User_Profile>> GetUsersAsync (string id)
{
try
{
string queryString = "SELECT * FROM c WHERE c.id='"+id+"'";
var query = ctner_Users.GetItemQueryIterator<User_Profile>(new QueryDefinition(queryString));
List<User_Profile> results = new List<User_Profile>();
while (query.HasMoreResults)
{
var response = await query.ReadNextAsync();
results.AddRange(response.ToList());
}
return results;
}
catch (Exception e)
{
return null;
}
}
Issue seems to be related to the " ReadItemAsync" that i am either not using properly, or has an issue in the version i am using.
I found a solution, which is quite far from the reported error message.
I had to turn the controller into an async Task instead of the previous one.
The controller must be:
[HttpPost("auth")]
public async Task<ActionResult<User_Profile>> AuthUser ([FromBody] User_Profile authUser)
{
var user = await _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
Instead of:
[HttpPost("auth")]
public ActionResult<User_Profile> AuthUser ([FromBody] User_Profile authUser)
{
var user = _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
I am not sure whether I can explain that one, but it solved the issue.
I have a RESTful service in .NET and i would like all actions return an object of type JsonResult, JsonResult is an object defined by me like this:
public class JsonResult<T>
{
public Notify Notify {get; set;}
public T Data {get; set;}
public static CreateResponse(T Data, Notify Notify = null, HttpStatusCode Code = HttpStatusCode.OK)
{
//Code param not manage, at the moment
return new JsonResult<T>
{
Data = Data,
Notify = Notify
};
}
}
public class Notify
{
public string Message {get; set;}
public Severity Severity {get; set;}
}
public enum Severity
{
Error,
Info,
Warning,
Fatal,
}
so at the moment i have actions look like that:
public JsonResult<string> Get()
{
return JsonResult<string>.CreateResponse("Ciao Mondo!");
}
it works and i like this form 'cause when i read the firm i already know what i want to return to the client (JsonResult, T is the type of my data)... but the problem is when i want to manage the status code.
Before to create this new project, to manage the status code i used the HttpResponseMessage and the method Request.CreateResponse to return some data to the client:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.BadRequest, "Ciao Mondo!");
}
but i don't like that 'cause it is not immediate to know the returned type.
so... my question is... how can i manage the status code into my JsonResult.CreateResponse(T, Notify[, HttpStatusCode]) method?
this method is moved into an CustomApiController (inherits from ApiController), so i have HttpContext and Request available.
Thanks so much to all
You could call Content which accepts a HttpStatusCode as well as a generic object. It does require you to change your method's return type to IHttpActionResult which is generally preferred.
public IHttpActionResult Get()
{
if(someErrorCondition)
return Content(HttpStatusCode.BadRequest, JsonResult<string>.CreateResponse("Ciao Mondo!"));
return Ok(JsonResult<string>.CreateResponse("Ciao Mondo!"));
}
Its preferred to use IHttpActionResult, don't forcefully define it to
JsonResult. This should be configurable through content-negotiation.
You can try as below:
public IHttpActionResult Get()
{
if(error)
BadRequest("Bad Request !!");
return Ok("Ciao Mondo!");
}
Your answer were be very helpful. I was looking for another solution, but you gave me a good idea and i have found this solution:
public class CustomApiController : ApiController
{
public class JsonResult<Target> : NegotiatedContentResult<Target>
{
public JsonResult(HttpStatusCode statusCode, Json<Target> content, ApiController controller) : base(statusCode, content.Data, controller)
{
this.Content = content;
}
public JsonResult(HttpStatusCode statusCode, Target content, ApiController controller) : base(statusCode, content, controller)
{
}
public JsonResult(HttpStatusCode statusCode, Target content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(statusCode, content, contentNegotiator, request, formatters)
{
}
public new Json<Target> Content { get; private set; }
}
public JsonResult<Target> CreateResponse<Target>(Target Data, string Notify, HttpStatusCode Code = HttpStatusCode.OK)
{
Json<Target> json = new Json<Target>
{
Notify = Notify,
Data = Data
};
return new JsonResult<Target>(Code, json, this);
}
}
so i can inherit from CustomApiController and write action like that:
public JsonResult<IEnumerable<string>> Get(bool test)
{
if (test)
{
return this.CreateResponse(new string[] { "test1", "test2", "test3" } as IEnumerable<string>, null, System.Net.HttpStatusCode.OK);
}
else
{
return this.CreateResponse(new string[] { "test1", "test2", "test3" } as IEnumerable<string>, null, System.Net.HttpStatusCode.BadRequest);
}
}
thanks a lot!
I followed the following tutorial and set up my azure backend .
https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/custom/
I then installed postman for the first time and set :
http://apptest.azurewebsites.net/.auth/login/custom
{"username" : "adrian" , "password" : "supersecret"}
Json (application/json)
However , i keep getting this error :
405 Method not Allowed
{
"Message": "The requested resource does not support http method 'GET'."
}
Backend Code :
using System;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Web.Http;
using Newtonsoft.Json;
using AppTestService.Models;
using AppTestService.DataObjects;
using Microsoft.Azure.Mobile.Server.Login;
namespace AppTestService.Controllers
{
[Route(".auth/login/custom")]
public class CustomAuthController : ApiController
{
private AppTestContext db;
private string signingKey, audience, issuer;
public CustomAuthController()
{
db = new AppTestContext();
signingKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var website = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");
audience = $"https://{website}/";
issuer = $"https://{website}/";
}
[System.Web.Http.HttpPost]
public IHttpActionResult Post([FromBody] User body)
{
if (body == null || body.Username == null || body.Password == null ||
body.Username.Length == 0 || body.Password.Length == 0)
{
return BadRequest();
}
if (!IsValidUser(body))
{
return Unauthorized();
}
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, body.Username)
};
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims, signingKey, audience, issuer, TimeSpan.FromDays(30));
return Ok(new LoginResult()
{
AuthenticationToken = token.RawData,
User = new LoginResultUser { UserId = body.Username }
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool IsValidUser(User user)
{
return db.Users.Count(u => u.Username.Equals(user.Username) && u.Password.Equals(user.Password)) > 0;
}
}
public class LoginResult
{
[JsonProperty(PropertyName = "authenticationToken")]
public string AuthenticationToken { get; set; }
[JsonProperty(PropertyName = "user")]
public LoginResultUser User { get; set; }
}
public class LoginResultUser
{
[JsonProperty(PropertyName = "userId")]
public string UserId { get; set; }
}
}
If i add [System.Web.Http.HttpGet] on top of the function , then i get a different error. :
415 Unsupported media type
{
"Message": "The request contains an entity body but no Content-Type header. The inferred media type 'application/octet-stream' is not supported for this resource."
}
These are the headers :
Allow →POST
Content-Length →72
Content-Type →application/json; charset=utf-8
Date →Sat, 28 Jan 2017 22:08:48 GMT
Server →Microsoft-IIS/8.0
X-Powered-By →ASP.NET
You probably do want to make a POST request from PostMan instead of a GET. Don't add [HttpGet] on the action, just set the method to POST in PostMan.
And make sure you set the header Content-Type: application/json in PostMan.
Make sure in the headers of your request you are setting ContentType = "application/json" in postman or even when creating a request from any client.
Change your controller definition to
[RoutePrefix("auth/login/custom")]
public class CustomAuthController : ApiController
{
}
And just for test, introduce a route on POST like
[System.Web.Http.HttpPost]
[Route("post")]
public IHttpActionResult Post([FromBody] User body)
and try making a request to
http://apptest.azurewebsites.net/auth/login/custom/post
In Azure service api make sure it is with [HttpPost].
If you are able to send body param that means you have selected Post in postman while calling API which is correct. Dont change either of postman call or azure api to get. it will be mismatch.
If you have access to azure logs check if http Post is redirected to the https url as Get, in this case try calling https directly.
Azure logs looks as follows in this case:
Received request: POST http://xxx.azurewebsites.net/api/Data/test
Information Redirecting: https://xxx.azurewebsites.net/api/Data/test
Received request: GET https://xxx.azurewebsites.net/api/Data/test
in this case call https://xxx.azurewebsites.net/api/Data/test