What proper technique for MAUI to use API for Login - authentication

I have a Blazor Server web application that uses Identity(DbContext, ApplicationUser) to login and register users.
I am creating a .Net Core 7.0 Web API to handle the login and database interactions for a MAUI Blazor hybrid mobile app that will use the same SQL database and Identity login.
The API is hosted on Azure App Service.
My question is how do I make the MAUI app secure and use Identity and the api to login?
This is the API Code. This is a minimal API and does not use controllers
public static async Task<IResult> LoginUser(UserLoginModel user, SignInManager<ApplicationUser> signInManager)
{
if (signInManager != null)
{
try
{
var result = await signInManager.PasswordSignInAsync(user.Email, user.Password, user.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
return Results.Ok(result);
}
if (result.IsLockedOut)
{
return Results.Unauthorized();
}
else
{
return Results.Problem("Invalid Login");
}
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
}
else
return Results.Problem("sign in is null");
}
This is the API endpoint
app.MapPost(pattern: "Users/Login/{userloginnmodel}",
Login.LoginUser);
UserLoginModel is a simple email password and rememberme
public class UserLoginModel
{
public string Email { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Swagger test passes if I enter login and password. The API returns Success is valid login.
Next..here is the login for the MAUI app
public class UserData : IUserData
{
public async Task<HttpResponseMessage> Login(UserLoginModel userloginmodel)
{
try
{
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://..../Users/Login/{userloginmodel}"))
{
request.Headers.TryAddWithoutValidation("accept", "*/*");
request.Content = new StringContent(JsonConvert.SerializeObject(userloginmodel), Encoding.UTF8);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await httpClient.SendAsync(request);
return response;
}
}
}
catch
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
}
The MAUI mobile app return a InternalServerError when I try to login from the app. Please note that the other endpoints work ok with the MAUI app and only the login is giving the error

Related

How to use/Implement OAuth2 with Owin in ASp.net Core?

Hello I am very beginner in whole web development , I got a task to make an API and for the good or bad of it I chose Asp.Core to do it .
The api connects a mobile client with a CRM system and there is an AD system (Not azure nor windows AD) it is called NetIQ. Any request to the api has to be from a user who is authenticated by Net IQ before then the API issues a token to the client.
Anyway I would like to have kind of authentication mechanism, and I have been told that OWIN can handle OAUTh2 stuff .
I have the exact code that I need , but it is with MVC 5.
Can someone help me with migrating this thing to MVC core ?
I searched for long time and could not find what I want . Maybe because i am still a bigenner in the web stuff area .
The code that I want to migrate is
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
Startup.Auth.cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(15),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
ApplicationOAuthProvider.cs
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private static string aadInstance = "https://login.microsoftonline.com/{0}";
private static string tenant = ConfigurationManager.AppSettings["AzureAD.Tenant"];
private static string clientId = ConfigurationManager.AppSettings["AzureAD.ClientId"];
private static string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static AuthenticationContext authContext = null;
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
authContext = new AuthenticationContext(authority, new TokenCache());
var creds = new UserPasswordCredential(context.UserName, context.Password);
AuthenticationResult result;
try
{
result = await authContext.AcquireTokenAsync(clientId, clientId, creds);
}
catch (Exception ex)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
context.Validated(identity);
}
}
So have anyone a sample how would I do that in ASP core ? because the whole startup thing is making me confused to be honest .

Can I verify a Facebook jwt token created using native iOS sdk with a .net backend?

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

MVC5 Authentication cannot save UseCookieAuthentication

I have a problem with MVC 5 Authentication
After success login, i think i cannot save UseCookieAuthentication.
My Startup code:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/MyAccount/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
My login controler:
public ActionResult Login(Login l, string ReturnUrl="")
{
using (CBDB2012Entities dc = new CBDB2012Entities())
{
var user = dc.Users.Where(a => a.UserName.Equals(l.Username) && a.Password.Equals(l.Password)).FirstOrDefault();
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.UserName, l.RememberMe);
if (Url.IsLocalUrl(ReturnUrl))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("MyProfile", "MyAccount");
}
}
}
ModelState.Remove("Password");
return View();
}
after login, it RedirectToAction("MyProfile", "MyAccount");
But in MyProfile view, i cannot see anything about user and i cann't access to contac page with [Authorize]:
-Contact controler
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
I has test via session in login control, and it work fine:
var user = dc.Users.Where(a => a.UserName.Equals(l.Username) && a.Password.Equals(l.Password)).FirstOrDefault();
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.UserName, l.RememberMe);
Session["loginname"] = user.Employee.FirstName;
if (Url.IsLocalUrl(ReturnUrl))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("MyProfile", "MyAccount");
}
}
i can get Session["loginname"] in view from database.

WEB API 2 OAuth Client Credentials Authentication, How to add additional parameters?

I am developing a Web API 2 with "OAuth Client Credentials" flow where, in the first call for the authorization token, I need an additional parameter to be accessed after in the controller without the need to traffics it all the time in requests.
What is the best way to pass an additional parameter for the authentication process and uses it later in controllers without the need for traffics it in future requests and considering security issues ?
I found developers passing these parameters in the URL, and adding it to the OWinContext:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||
context.TryGetFormCredentials(out clientId, out clientSecret))
{
try
{
if (context.Parameters.Any(x => x.Key == "Participant"))
{
// Get Participant(Additional Parameters)
string participant = context.Parameters.First(x => x.Key == "participant").Value[0];
//Validating Client
Microsoft.AspNet.Identity.PasswordHasher passwordHaser = new Microsoft.AspNet.Identity.PasswordHasher();
Client client = _clientAppService.GetById(Guid.Parse(clientId));
if (client != null && passwordHaser.VerifyHashedPassword(client.SecretHash, clientSecret) == Microsoft.AspNet.Identity.PasswordVerificationResult.Success)
{
context.OwinContext.Set<Client>("oauth:client", client);
//Store Participant(Additional Parameters)
context.OwinContext.Set<Participant>("urn:participant", new Participant { Document = participant });
context.Validated();
}
else
{
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
else
{
context.SetError("invalid_request", "Participant are invalid.");
context.Rejected();
}
}
catch (Exception)
{
context.SetError("server_error");
context.Rejected();
}
}
else
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
}
return Task.FromResult(0);
}
And I am trying to get it via an ModelBinder, unfortunately the context always returns null:
public class ParticipantModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//Returning null
Participant participant = HttpContext.Current.GetOwinContext().Get<Participant>("urn:participant");
bindingContext.Model = participant;
return true;
}
}
Controller:
[Authorize]
public class ParticipantController : ApiController
{
public decimal GetBalance([ModelBinder(BinderType=typeof(ParticipantModelBinder))] Participant participant)
{
return 0;
}
}
And another solution I found on the web is to add a new Claim on the method GrantClientCredentials, but i dont know a way to get the value on the controllers:
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
//Client validated, generate token
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
Client client = context.OwinContext.Get<Client>("oauth:client");
if (client.AllowedGrant == OAuthGrant.Client)
{
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType));
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
// Add an custum Claim with the additional parameter
identity.AddClaim(new Claim("Participant", context.OwinContext.Get<Participant>("urn:participant").Document));
context.Validated(identity);
}
else
{
context.SetError(
"unauthorized_client",
"The authenticated client is not authorized to use this authorization grant type");
}
return Task.FromResult(0);
}

WebAPI : How to add the Account / Authentication logic to a self hosted WebAPI service

I just came across a great reference example of using authenticated WebAPI with AngularJS:
http://www.codeproject.com/Articles/742532/Using-Web-API-Individual-User-Account-plus-CORS-En?msg=4841205#xx4841205xx
An ideal solution for me would be to have such WebAPI service self hosted instead of running it as a Web application.
I just do not know where to place all of the authentication / authorization logic within a self hosted (OWIN / Topshelf) solution.
For example, in the Web app, we have these two files: Startup.Auth, and ApplicationOAuthProvider:
Startup.Auth:
public partial class Startup
{
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
ApplicationOAuthProvider:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
private readonly Func<UserManager<IdentityUser>> _userManagerFactory;
public ApplicationOAuthProvider(string publicClientId, Func<UserManager<IdentityUser>> userManagerFactory)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
if (userManagerFactory == null)
{
throw new ArgumentNullException("userManagerFactory");
}
_publicClientId = publicClientId;
_userManagerFactory = userManagerFactory;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Add Access-Control-Allow-Origin header as Enabling the Web API CORS will not enable it for this provider request.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (UserManager<IdentityUser> userManager = _userManagerFactory())
{
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
I'm looking for a way to integrate these into my OWIN self hosted app, and have these authentication features. start upon application startup, and function as they do in the Web app version.