I am using following code to get user details in azure mobile services. I am trying to migrate to azure mobile app, and get user details in easy api. how to do that ?
getIdentity({
success: function (identities) {
var req = require('request');
if (identities.facebook) {
var fbAccessToken = identities.facebook.accessToken;
var url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
req(url, function (err, resp, body) {
if (err || resp.statusCode !== 200) {
console.error('Error sending data to FB Graph API: ', err);
// req.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
} else {
try {
var userData = JSON.parse(body);
console.log(JSON.parse(userData));
res.json(userData);
//item.UserName = userData.name;
// request.execute();
} catch (ex) {
console.error('Error parsing response from FB Graph API: ', ex);
// request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
}
}
});
Following this example, I edited the script and it works for me with google authentication.
currentUser.getIdentity()
.then((data) =>
{
var http = require('request');
var url = 'https://www.googleapis.com/oauth2/v3/userinfo' +
'?access_token=' + data.google.access_token;
var reqParams = {
uri: url, headers: { Accept: 'application/json' } };
http.get(reqParams, function (err, resp, body) {
var userData = JSON.parse(body);
res.status(200).send( {message: userData});
});
}, console.error);
This link advises using the context to request for additional user details in an EasyTable Node.js script instead of an EasyAPI. Only resource I could find that doesn't rely on the classic Azure portal.
https://azure.microsoft.com/en-in/documentation/articles/app-service-mobile-node-backend-how-to-use-server-sdk/#Debugging
Edit: the above code didn't work for me, so maybe this will help anyone else out
It seems like there is a build in function already. below code is invoking .auth/me action on your client and returns you claims object. claims object is a dictionary of information, you selected on azure portal.
public async Task<AppServiceIdentity> GetIdentityAsync()
{
if (Client.CurrentUser == null || Client.CurrentUser?.MobileServiceAuthenticationToken == null)
{
throw new InvalidOperationException("Not Authenticated");
}
if (identities == null)
{
identities = await Client.InvokeApiAsync<List<AppServiceIdentity>>("/.auth/me");
}
if (identities.Count > 0)
return identities[0];
return null;
}
public class AppServiceIdentity
{
[JsonProperty(PropertyName = "id_token")]
public string IdToken { get; set; }
[JsonProperty(PropertyName = "provider_name")]
public string ProviderName { get; set; }
[JsonProperty(PropertyName = "user_id")]
public string UserId { get; set; }
[JsonProperty(PropertyName = "user_claims")]
public List<UserClaim> UserClaims { get; set; }
}
public class UserClaim
{
[JsonProperty(PropertyName = "typ")]
public string Type { get; set; }
[JsonProperty(PropertyName = "val")]
public string Value { get; set; }
}
So you will get the info from claims object like this for name
var name = identity.UserClaims.FirstOrDefault(c => c.Type.Equals("name")).Value;
Related
I have a complex record SearchProductsRequest in a GET request that receives the parameters by query (
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc ).
app.MapGet(
$"/{ProductCatalogueApi.Version}/products",
(SearchProductsRequest request)
=> ProductApiDelegates.SearchProducts(
request));
In the record, I've implemented the bind async
public static ValueTask<SearchProductsRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter); and now the parameters from the URL automatically convert the parameters to SearchProductsRequest.
The request is working as intended, but we are using (Swashbuckle -> ) Swagger UI for development.
Swagger UI does not recognize the members from SearchProductsRequest to display them as input boxes. Is there a way to make swagger UI know them and display them so a user consulting the swagger endpoint can pass value through it?
I was hoping to get the following:
Until now, I've only managed to have the fields displayed in swagger if I have all of them in the Map.Get() explicitly.
EDIT:
Adding asked content
Record:
public record SearchProductsRequest
{
public IEnumerable<int>? Ids { get; private set; }
public string? Name { get; private set; }
public PaginationInfoRequest? PaginationInfo { get; private set; }
public SortingInfoRequest? SortingInfo { get; private set; }
public SearchProductsRequest(
IEnumerable<int>? ids,
string? name,
PaginationInfoRequest? PaginationInfo,
SortingInfoRequest? SortingInfo)
{
this.Ids = ids;
this.Name = name;
this.PaginationInfo = PaginationInfo;
this.SortingInfo = SortingInfo;
}
public static ValueTask<SearchProductsRequest?> BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
var ids = ParseIds(httpContext);
var name = httpContext?.Request.Query["name"] ?? string.Empty;
PaginationInfoRequest? pagination = null;
SortingInfoRequest? sorting = null;
if (int.TryParse(httpContext?.Request.Query["page"], out var page)
&& int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
{
pagination = new PaginationInfoRequest(page, pageItems);
}
var sortField = httpContext?.Request.Query["sortField"].ToString();
if (!string.IsNullOrEmpty(sortField))
{
sorting = new SortingInfoRequest(
sortField,
httpContext?.Request.Query["sort"].ToString() == "asc");
}
return ValueTask.FromResult<SearchProductsRequest?>(
new SearchProductsRequest(
ids,
name!,
pagination,
sorting));
}
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
private static int[]? ParseIds(HttpContext httpContext)
{
int[]? ids = null;
var commaSeparatedIds = httpContext?.Request.Query["ids"]
.ToString();
if (!string.IsNullOrEmpty(commaSeparatedIds))
{
ids = commaSeparatedIds
.Split(",")
.Select(int.Parse)
.ToArray() ?? Array.Empty<int>();
}
return ids;
}
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
}
Delegate:
internal static async Task<IResult> SearchProducts(
ILogger<ProductApiDelegates> logger,
IMapper mapper,
SearchProductsRequest request,
IValidator<SearchProductsRequest> validator,
IProductService productService)
{
using var activity = s_activitySource.StartActivity("Search products");
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.GetErrors();
logger.LogError("Bad Request: {Errors}", errors);
return Results.BadRequest();
}
try
{
logger.LogInformation("Searching product details by name");
var filtersContainer = mapper.Map<SearchProductsFiltersContainer>(request);
var products = await productService.SearchProductsAsync(filtersContainer);
if (products == null)
{
return Results.NotFound();
}
var searchProducts = BuildSearchProducts(mapper, products);
var paginationInfo = await BuildPaginationInfo(filtersContainer, productService);
var response = new SearchProductsResponse(searchProducts, paginationInfo);
return Results.Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error searching the products");
return Results.Problem();
}
}
I have wired up FluentValidation as per instructions, and when debuging test I can see that model is invalid based on the test setup, but exception is not thrown, but rather method on the controller is being executed. This is on 3.1 with EndPoint routing enabled. Is there anything else one needs to do to get this to work and throw. What happens is that validation obviously runs; it shows as ModelState invalid and correct InstallmentId is invalid, but it keeps processing in Controller instead of throwing exception.
services.AddMvc(
options =>
{
options.EnableEndpointRouting = true;
//// options.Filters.Add<ExceptionFilter>();
//// options.Filters.Add<CustomerRequestFilter>();
})
.AddFluentValidation(
config =>
{
config.RegisterValidatorsFromAssemblyContaining<Startup>();
})
Command and Validator
public class ProcessManualPayment
{
public class Command
: CustomerRequest<Result?>
{
public Guid PaymentPlanId { get; set; }
public Guid InstallmentId { get; set; }
public Guid PaymentCardId { get; set; }
}
public class Validator : AbstractValidator<Command>
{
public Validator()
{
this.RuleFor(x => x.CustomerId)
.IsValidGuid();
this.RuleFor(x => x.PaymentPlanId)
.IsValidGuid();
this.RuleFor(x => x.InstallmentId)
.IsValidGuid();
this.RuleFor(x => x.PaymentCardId)
.IsValidGuid();
}
}
Controller
[Authorize]
[HttpPost]
[Route("payments")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ProcessManualPayment(
[FromBody]
ProcessManualPayment.Command command)
{
Test
[Fact]
public async Task When_Command_Has_Invalid_Payload_Should_Fail()
{
var client = this.factory.CreateClient();
// Arrange
var validCmd = new ProcessManualPayment.Command()
{
CustomerId = Guid.NewGuid(),
PaymentPlanId = Guid.NewGuid(),
InstallmentId = Guid.NewGuid(),
PaymentCardId = Guid.NewGuid(),
};
var validCmdJson = JsonConvert.SerializeObject(validCmd, Formatting.None);
var jObject = JObject.Parse(validCmdJson);
jObject["installmentId"] = "asdf";
var payload = jObject.ToString(Formatting.None);
// Act
var content = new StringContent(payload, Encoding.UTF8, MediaTypeNames.Application.Json);
var response = await client.PostAsync(MakePaymentUrl, content);
var returned = await response.Content.ReadAsStringAsync();
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
[Fact]
public async Task When_Payload_Is_Null_Should_Fail()
{
// Arrange
var client = this.factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(MakePaymentUrl, null);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
GuidValidator
public class GuidValidator : PropertyValidator
{
public GuidValidator()
: base("'{PropertyName}' value {AttemptedValue} is not a valid Guid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
context.MessageFormatter.AppendArgument("AttemptedValue", context.PropertyValue ?? "'null'");
if (context.PropertyValue == null)
{
return false;
}
Guid.TryParse(context.PropertyValue.ToString(), out var value);
return IsValid(value);
}
private static bool IsValid(Guid? value) =>
value.HasValue
&& !value.Equals(Guid.Empty);
}
Mystery solved, I was missing [ApiController] attribute on the controller.
I'm trying to implement an OAUth 2.0 flow for custom webapplication for Azure Devops. I'm following this https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops documentation as well as this https://github.com/microsoft/azure-devops-auth-samples/tree/master/OAuthWebSample OauthWebSample but using ASP.NET Core (I also read one issue on SO that looked similar but is not: Access Azure DevOps REST API with oAuth)
Reproduction
I have registered an azdo app at https://app.vsaex.visualstudio.com/app/register and the authorize step seems to work fine, i.e. the user can authorize the app and the redirect to my app returns something that looks like a valid jwt token:
header: {
"typ": "JWT",
"alg": "RS256",
"x5t": "oOvcz5M_7p-HjIKlFXz93u_V0Zo"
}
payload: {
"aui": "b3426a71-1c05-497c-ab76-259161dbcb9e",
"nameid": "7e8ce1ba-1e70-4c21-9b51-35f91deb6d14",
"scp": "vso.identity vso.work_write vso.authorization_grant",
"iss": "app.vstoken.visualstudio.com",
"aud": "app.vstoken.visualstudio.com",
"nbf": 1587294992,
"exp": 1587295892
}
The next step is to get an access token which fails with a BadReqest: invalid_client, Failed to deserialize the JsonWebToken object.
Here is the full example:
public class Config
{
public string ClientId { get; set; } = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
public string Secret { get; set; } = "....";
public string Scope { get; set; } = "vso.identity vso.work_write";
public string RedirectUri { get; set; } = "https://....ngrok.io/azdoaccount/callback";
}
/// <summary>
/// Create azdo application at https://app.vsaex.visualstudio.com/
/// Use configured values in above 'Config' (using ngrok to have a public url that proxies to localhost)
/// navigating to localhost:5001/azdoaccount/signin
/// => redirect to https://app.vssps.visualstudio.com/oauth2/authorize and let user authorize (seems to work)
/// => redirect back to localhost:5001/azdoaccount/callback with auth code
/// => post to https://app.vssps.visualstudio.com/oauth2/token => BadReqest: invalid_client, Failed to deserialize the JsonWebToken object
/// </summary>
[Route("[controller]/[action]")]
public class AzdoAccountController : Controller
{
private readonly Config config = new Config();
[HttpGet]
public ActionResult SignIn()
{
Guid state = Guid.NewGuid();
UriBuilder uriBuilder = new UriBuilder("https://app.vssps.visualstudio.com/oauth2/authorize");
NameValueCollection queryParams = HttpUtility.ParseQueryString(uriBuilder.Query ?? string.Empty);
queryParams["client_id"] = config.ClientId;
queryParams["response_type"] = "Assertion";
queryParams["state"] = state.ToString();
queryParams["scope"] = config.Scope;
queryParams["redirect_uri"] = config.RedirectUri;
uriBuilder.Query = queryParams.ToString();
return Redirect(uriBuilder.ToString());
}
[HttpGet]
public async Task<ActionResult> Callback(string code, Guid state)
{
string token = await GetAccessToken(code, state);
return Ok();
}
public async Task<string> GetAccessToken(string code, Guid state)
{
Dictionary<string, string> form = new Dictionary<string, string>()
{
{ "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" },
{ "client_assertion", config.Secret },
{ "grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer" },
{ "assertion", code },
{ "redirect_uri", config.RedirectUri }
};
HttpClient httpClient = new HttpClient();
HttpResponseMessage responseMessage = await httpClient.PostAsync(
"https://app.vssps.visualstudio.com/oauth2/token",
new FormUrlEncodedContent(form)
);
if (responseMessage.IsSuccessStatusCode) // is always false for me
{
string body = await responseMessage.Content.ReadAsStringAsync();
// TODO parse body and return access token
return "";
}
else
{
// Bad Request ({"Error":"invalid_client","ErrorDescription":"Failed to deserialize the JsonWebToken object."})
string content = await responseMessage.Content.ReadAsStringAsync();
throw new Exception($"{responseMessage.ReasonPhrase} {(string.IsNullOrEmpty(content) ? "" : $"({content})")}");
}
}
}
When asking for access tokens the Client Secret and not the App Secret must be provided for the client_assertion parameter:
I am trying to insert multiple users in database using a API. Now, suppose there are three users to be inserted and assume that one user didn't get inserted but other two are suceessfully inserted. So, I have a requirement to show response which shows that user first is successfully inserted , user 2nd have an error. That is why I have useed list of httpResponseMessage and in each httpresponsemessageobject, I will add complete user json indicating that this was the user, who have failed or success status
So, as per my implementation
I am using foreach loop to insert multiple users in db and returning reponse like this but my respose from api do not shows "user" object in content:
public async Task<List<HttpResponseMessage>> InserUsers(JArray obj)
{
List<myDTO> users = obj.ToObject<List<myDTO>>();
List<HttpResponseMessage> list = new List<HttpResponseMessage>();
foreach (var user in users)
{
var response = Insert(user);
list.Add(new HttpResponseMessage
{
Content = new StringContent(JsonConvert.SerializeObject(user), System.Text.Encoding.UTF8, "application/json"),
ReasonPhrase = response.ReasonPhrase,
StatusCode = response.StatusCode
});
}
return returnResposeList;
}
public HttpResponseMessage Insert(User user)
{
// do insert and if there is error
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Error,
ReasonPhrase = $"Error"
};
else
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
ReasonPhrase = $"Successfully inserted"
};
}
}
Thanks & regards
As per some of the comments, I don't understand why you need a status code for each insert rather than returning a bool or something like the inserted Id.
I think something like this works with what you have shown already along with return the user objects:
public async Task<ObjectResult> InserUsers(JArray obj)
{
List<myDTO> users = obj.ToObject<List<myDTO>>();
List<InsertUserResponseDto> list = new List<InsertUserResponseDto>();
foreach (var user in users)
{
var isSuccess = Insert(user);
list.Add(new InsertUserResponseDto
{
User = user,
StatusCode = isSuccess ? StatusCodes.Status200OK : StatusCodes.Status500InternalServerError,
});
}
var isSuccessfullInsert = list.Any(x => x.StatusCode == StatusCodes.Status207MultiStatus);
return StatusCode(isSuccessfullInsert ? StatusCodes.Status207MultiStatus : StatusCodes.Status200OK, list);
}
public bool Insert(User user)
{
try
{
// Complete insert
return true;
}
catch (Exception ex)
{
// Log Exception
return false;
}
}
public class InsertUserResponseDto
{
public User User { get; set; }
public int StatusCode { get; set; }
}
I have used the response code of 207 if any of the inserts fail otherwise I have used a 200. Each object within the list will contain the original user object along with an associated status code that you wanted including handling for success and failure.
For HttpResponseMessage, it used to describe the whole response, you should avoid returning HttpResponseMessage with multiple HttpResponseMessage.
For another option, you could try create your own HttpResponseMessage like
public class ApiResponseMessage
{
//
// Summary:
// Gets or sets the reason phrase which typically is sent by servers together with
// the status code.
//
// Returns:
// The reason phrase sent by the server.
public string ReasonPhrase { get; set; }
//
// Summary:
// Gets or sets the status code of the HTTP response.
//
// Returns:
// The status code of the HTTP response.
public HttpStatusCode StatusCode { get; set; }
//
// Summary:
// Gets or sets the content of a HTTP response message.
//
// Returns:
// The content of the HTTP response message.
public object Content { get; set; }
}
And then
[HttpPost("InserUsers")]
public async Task<List<ApiResponseMessage>> InserUsers()
{
List<User> users = new List<User> {
new User{ Name = "Jack" },
new User{ Name = "Tom"},
new User{ Name = "Tony"}
};
List<ApiResponseMessage> list = new List<ApiResponseMessage>();
foreach (var user in users)
{
var response = Insert(user);
list.Add(new ApiResponseMessage
{
Content = new { user },
ReasonPhrase = response.ReasonPhrase,
StatusCode = response.StatusCode
});
}
return list;
}
public ApiResponseMessage Insert(User user)
{
// do insert and if there is error
if (user.Name == "Tom")
{
return new ApiResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Error"
};
}
else
{
return new ApiResponseMessage()
{
StatusCode = HttpStatusCode.OK,
ReasonPhrase = $"Successfully inserted"
};
}
}
I'm implementing Facebook logins on an iOS app with a .net core web api backend.
I created the app in Facebook with a client id and secret.
I added iOS and web pages to my app
The iOS app successfully connects and aquires a token
I added the app.UseFacebook code to my startup.cs and configured it with the app id etc.
I added the authorize attribute to an action I'm restricting access to
I call this web api action from iOS with an https get, and add an http header Authorization Bearer (token I acquired from Facebook)
The get returns status code 401 as if my token was invalid. I'm wondering if iOS tokens can be used with a web page app as well? I have those 2 configured for my Facebook app.
I'm not sure if this was the right way to do it, but my implementation is working.
The workflow is,
iOS app gets a facebook access token using the facebook sdk
iOS app creates a user in the .NET back-end using the facebook access token
.NET backend verifies the facebook access token is valid and downloads user data
.NET backend creates a jwt bearer token and returns it to the iOS app
iOS app calls .NET backend with the jwt bearer token in the Authorization http header
I check the Facebook access token is valid by calling https://graph.facebook.com
-- refer to: Task VerifyAccessToken(string email, string accessToken)
AccountController.cs
[AllowAnonymous, HttpPost("[action]")]
public async Task<ActionResult> FacebookAuth([FromBody] ExternalLoginModel model)
{
try
{
await _interactor.VerifyAccessToken(model.Email, model.Token);
var result = await _interactor.SignInWithFacebook(model.Email);
return Ok(result);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message.ErrorMessage(Strings.ValidationException));
}
}
[AllowAnonymous, HttpPost("[action]")]
public async Task<ActionResult> CreateAccountWithFacebook(AccountModel account, string token)
{
try
{
await _interactor.VerifyAccessToken(account.Email, token);
if (ModelState.IsValid)
{
var result = await _interactor.CreateFacebookLogin(account);
return Ok(result);
}
return BadRequest(ModelState);
}
catch (ValidationException ex)
{
return BadRequest(ex.Message.ErrorMessage(Strings.ValidationException));
}
}
call facebook graph service to verify the access token is valid
public async Task<FacebookMeResponse> VerifyAccessToken(string email, string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
{
throw new ValidationException("Invalid Facebook token");
}
string facebookGraphUrl = "https://graph.facebook.com/me?fields=cover,age_range,first_name,location,last_name,hometown,gender,birthday,email&access_token=" + accessToken;
WebRequest request = WebRequest.Create(facebookGraphUrl);
request.Credentials = CredentialCache.DefaultCredentials;
using (WebResponse response = await request.GetResponseAsync())
{
var status = ((HttpWebResponse)response).StatusCode;
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
var facebookUser = JsonConvert.DeserializeObject<FacebookMeResponse>(responseFromServer);
bool valid = facebookUser != null && !string.IsNullOrWhiteSpace(facebookUser.Email) && facebookUser.Email.ToLower() == email.ToLower();
facebookUser.PublicProfilePhotoUrl = "http://graph.facebook.com/" + facebookUser.Id + "/picture";
if (!valid)
{
throw new ValidationException("Invalid Facebook token");
}
return facebookUser;
}
}
Create a jwt bearer token for your middleware, the iOS app will use the jwt bearer token for calling your .NET apis (it won't use the facebook access_token)
public async Task<FacebookResponse> SignInWithFacebook(string email)
{
var claims = new List<Claim>();
var user = await _userManager.FindByEmailAsync(email);
var identity = new ClaimsIdentity(claims, "oidc");
var jwtBearerToken= Guid.NewGuid().ToString();
var properties = new AuthenticationProperties();
properties.Items.Add(".Token.access_token", jwtBearerToken);
await _signInManager.SignInAsync(user, properties, "oidc");
var principal = await _signInManager.CreateUserPrincipalAsync(user);
var token = new Token();
token.Key = jwtBearerToken;
token.Expiry = DateTime.UtcNow.AddMinutes(30);
token.UserId = user.Id;
token.TokenType = "FacebookLogin";
await _tokensRepository.Save(token);
var result = _signInManager.IsSignedIn(principal);
return new FacebookResponse("success", result, jwtBearerToken);
}
Create a user if it doesnt exist
public async Task<FacebookResponse> CreateFacebookLogin(AccountModel model)
{
User user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
var createResult = await _userManager.CreateAsync(_mapper.Map<AccountModel, User>(model));
if (!createResult.Succeeded)
{
// handle failure..
}
}
return await SignInWithFacebook(model.Email);
}
Classes for de-serialising the response from the facebook graph REST service
public class FacebookAgeRange
{
public int Min { get; set; }
}
public class FacebookMeResponse
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Gender { get; set; }
public FacebookAgeRange AgeRange { get; set; }
public string PublicProfilePhotoUrl { get; set; }
}
public class FacebookResponse : IResponse
{
public bool Ok { get; set; }
public string Message { get; set; }
public string JwtToken { get; set; }
public FacebookResponse(string message, bool ok = true, string jwtToken = "")
{
this.Message = message;
this.Ok = ok;
this.JwtToken = jwtToken;
}
}
ASP.NET Security repo contains different auth middlewares, where you can find how to check and verify jwt tokens and create identity with claims. Look into FacebookHandler.cs if you need Facebook JWT toen validation