I'm developing an application in Angular and .Net, it consists of creating projects -like Asana- each project has a creator, so there I made the connection, I related the table of users and projects to create the foreign key (userCreationId).
It's a POST request, this is my service:
public string ObtenerUsuarioId()
{
if (httpContext.User.Identity.IsAuthenticated)
{
var idClaim = httpContext.User.Claims
.Where(x => x.Type == ClaimTypes.NameIdentifier).Single();
return idClaim.Value;
}
else
{
throw new Exception("El usuario no está autenticado");
}
}
And the controller:
public async Task<IActionResult> Post([FromBody] ProyectoCreacionDTO proyectoCreacionDTO)
{
var proyecto = mapper.Map<Proyecto>(proyectoCreacionDTO);
var usuarioId = servicioUsuarios.ObtenerUsuarioId();
proyecto.UsuarioCreacionId = usuarioId;
context.Add(proyecto);
await context.SaveChangesAsync();
return Ok(proyecto);
}
But the request in Angular does not bring me the claims, and it falls in the else of my service in .net.
It doesn't read the email and the id.
error : "System.Exception: User isn't authenticated \r\n.
Which is the else of my service.
I tried the request on POSTMAN and it works:
It does read the email!
And I get the answer I was expecting:
A successful answer
But then again when I tried this in Angular it doesn't work, and I'm saving the token in LocalStorage
login(email:string, password:string):Observable<Login>{
return this.http.post<Login>(`${this.url}/login`, {email, password})
.pipe(
tap(resp => {
this.localStorage.getToken(resp.token)
})
)
crearProyecto(proyecto:Proyecto):Observable<Proyecto>{
return this.http.post<Proyecto>(`${this.url}/agregar`, proyecto)
The token is actually saved, I can protect routes with AuthGuards, so I dont know why when I send the request from Angular it doesn't work
Here is the token
I even have the Cookie that Identity generates:
The cookie
Related
I'm working on .NET Maui mobile client with ASP.NET Core backend, and am trying to integrate Google authorization via the Web Authenticator API built into Maui. I was able to get the login to function by basically following the guide on the Web Authenticator page linked above, but haven't been able to logout from the account I originally logged in with. I've created a logout endpoint for my server, and after I hit this endpoint (where I call HttpContext.SignOutAsync() ) and compare the value of ClaimsPrinciple, the data from Google is gone (see attached screenshots).
before logout
after logout
However, the next time I try to log in I do not need to go through Google authentication, it automatically logs me in again. I've seen similar issues with Web Authenticator (linked here and here and in some other issues linked to these). I'm new at this framework and mobile dev in general and I'm still unclear from these resources what the best way to handle this is - most of these issues are also relating to Xamarins forms rather than Maui, so not sure if theres any more updated solution.
These are the implementations of login and logout and the corresponding requests
public class MobileAuthController : ControllerBase
{
const string callbackScheme = "myapp";
[HttpGet("{scheme}")]
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
//Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities
.FirstOrDefault().Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
//Build the result url
var user = this.User; //CHECK VALUE OF USER
var url = callbackScheme + "://#";
//Redirect to final url
Request.HttpContext.Response.Redirect(url);
}
}
[HttpPost("logout")]
public async Task Logout()
{
try
{
await HttpContext.SignOutAsync();
var url = callbackScheme + "://#";
var user = this.User; //CHECK VALUE OF USER
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to sign out user {ex.Message}");
}
}
var url = "http://localhost:5000/mobileauth/logout";
HttpContent content = null;
var response = await httpClient.PostAsync(url, content);
WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
new Uri("https://localhost:5001/mobileauth/Google"),
new Uri("myapp://"));
Edit - I'm primarily confused on how i need to be handling Cookies and access/refresh tokens and the built in claims stuff - like I said, I'm new at this
I've written an ASP.NET Core webapp that uses Auth0 as its primary authorization mechanism for users, which middlemans a whole bunch of external auth endpoints like Google and Facebook. That works fine and I have no issues there.
At its core the webapp makes use of Google Analytics to perform its own analytics and business logic. The Google Analytics account that is being analysed by my webapp could and is likely different from the users' own Google account. To be clear what I mean is that it is likely the user will login with whatever login provider they wish, and then they'll attach a specific Google business account with access to their businesses Google Analytics system.
The webapp performs analytics both whilst the user is logged in, and whilst the user is offline.
So I've always kept the user auth (Auth0) step seperate from the auth of the Analytics account step. The general process is as follows:
User logs in via Auth0 using whatever provider (Google, Facebook, email/pass) and accesses the private dashboard.
User sets up a "Company" and clicks on a button to authorize our webapp access to a specific Google account with Analytics on it.
User is redirected back to the private dashboard and the refresh token of the Google account is stored for future use.
Previously I had been pushing the Analytics auth through Auth0 as well, and I used a cached Auth0 refresh token to do work offline. However it expires after some days and Auth0 don't appear to provide long-term offline access.
So I figure the easiest thing to do would be to simply not use auth0 for the Analytics auth step, auth directly with the Google API and store the Google refresh token long-term. However I cannot find any concrete examples of how to achieve this!
Official Google API .NET Example - This appears to be very old and not really supported by ASPNET Core. I can't see a clear way to mould this into anything usable and searching SO finds clear issues with it.
SO answer to a similar question - It's a great answer, but the implementation is for user auth and I don't believe would work in my scenario.
I finally cracked it! I ended up throwing away all the libraries and found that it was simplest to use the plain old REST API. Code example below for those curious:
The users' browser GETs the following and is redirected to Google for an auth token:
public IActionResult OnGet([FromQuery]int id, [FromQuery]string returnAction)
{
var org = context.Organizations.Include(o => o.UserOrgs).First(o => o.Id == id);
var user = GetUser();
if (!IsUserMemberOfOrg(user, org)) return BadRequest("User is not a member of this organization!");
var redirectUri = Uri.EscapeUriString(GetBaseUri()+"dash/auth/google?handler=ReturnCode");
var uri = $"https://accounts.google.com/o/oauth2/v2/auth?"+
$"scope={Uri.EscapeUriString("https://www.googleapis.com/auth/analytics.readonly")}"+
$"&prompt=consent"+
$"&access_type=offline"+
//$"&include_granted_scopes=true"+
$"&state={Uri.EscapeUriString(JsonConvert.SerializeObject(new AuthState() { OrgId = id, ReturnAction = returnAction }))}"+
$"&redirect_uri={redirectUri}"+
$"&response_type=code"+
$"&client_id={_configuration["Authentication:Google:ClientId"]}";
return Redirect(uri);
}
Google redirects back to the following, and which point I perform a POST from the webserver to a Google API to exchange the auth token for a refresh token and store it for later:
public async Task<IActionResult> OnGetReturnCode([FromQuery]string state, [FromQuery]string code, [FromQuery]string scope)
{
var authState = JsonConvert.DeserializeObject<AuthState>(state);
var id = authState.OrgId;
var returnAction = authState.ReturnAction;
var org = await context.Organizations.Include(o => o.UserOrgs).SingleOrDefaultAsync(o => o.Id == id);
if (org == null) return BadRequest("This Org doesn't exist!");
using (var httpClient = new HttpClient())
{
var redirectUri = Uri.EscapeUriString(GetBaseUri()+"dash/auth/google?handler=ReturnCode");
var dict = new Dictionary<string, string>
{
{ "code", code },
{ "client_id", _configuration["Authentication:Google:ClientId"] },
{ "client_secret", _configuration["Authentication:Google:ClientSecret"] },
{ "redirect_uri", redirectUri },
{ "grant_type", "authorization_code" }
};
var content = new FormUrlEncodedContent(dict);
var response = await httpClient.PostAsync("https://www.googleapis.com/oauth2/v4/token", content);
var resultContent = JsonConvert.DeserializeObject<GoogleRefreshTokenPostResponse>(await response.Content.ReadAsStringAsync());
org.GoogleAuthRefreshToken = resultContent.refresh_token;
await context.SaveChangesAsync();
return Redirect($"{authState.ReturnAction}/{authState.OrgId}");
}
}
Finally, we can get a new access token with the refresh token later on without user intervention:
public async Task<string> GetGoogleAccessToken(Organization org)
{
if(string.IsNullOrEmpty(org.GoogleAuthRefreshToken))
{
throw new Exception("No refresh token found. " +
"Please visit the organization settings page" +
" to setup your Google account.");
}
using (var httpClient = new HttpClient())
{
var dict = new Dictionary<string, string>
{
{ "client_id", _configuration["Authentication:Google:ClientId"] },
{ "client_secret", _configuration["Authentication:Google:ClientSecret"] },
{ "refresh_token", org.GoogleAuthRefreshToken },
{ "grant_type", "refresh_token" }
};
var resp = await httpClient.PostAsync("https://www.googleapis.com/oauth2/v4/token",
new FormUrlEncodedContent(dict));
if (resp.IsSuccessStatusCode)
{
dynamic returnContent = JObject.Parse(await resp.Content.ReadAsStringAsync());
return returnContent.access_token;
} else
{
throw new Exception(resp.ReasonPhrase);
}
}
}
I use the Openiddict library for OAuth2 authentification on the server side. We use Asp.Net Core - server side, Angular 2 - client side. After processed Authorize request, the server redirects to the following URL with user confirmation form. See example code below:
[Authorize]
[HttpGet]
[Route(RouteConst.OAuth.AUTHORIZE)]
[EnableCors(CommonConst.ALLOW_ALL)]
public async Task<IActionResult> Authorize()
{
var request = this.HttpContext.GetOpenIdConnectRequest();
// Retrieve the application details from the database.
var application = await this._applicationManager.FindByClientIdAsync(request.ClientId, this.HttpContext.RequestAborted);
if (application == null)
{
return this.BadRequest(new ErrorUI
{
Error = OpenIdConnectConstants.Errors.InvalidClient,
ErrorDescription = "Details concerning the calling client application cannot be found in the database"
});
}
return this.Redirect($"/app/profile?oauth={request.Scope}&application={application.DisplayName}&requestId={request.RequestId}");
}
After redirect user can see the confirmation form then click Accept(Deny) button. In Openiddict Code samples is used MvcBinders and Razor views, but when I tried to create similar POST request to the server from Angular view - OpenIdConnectRequest is null.
Why is OpenIdConnectRequest null after redirect to the local URL? Can I reach the Accept Authorize endpoint within CORS POST request without using MVC?
Startup setup:
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
options.EnableAuthorizationEndpoint(RouteConst.OAuth.AUTHORIZE)
.EnableLogoutEndpoint(RouteConst.OAuth.LOGOUT)
.EnableTokenEndpoint(RouteConst.OAuth.TOKEN)
.EnableUserinfoEndpoint(RouteConst.USERINFO);
options.AllowAuthorizationCodeFlow();
options.EnableRequestCaching();
});
Accept endpoint:
[Authorize]
[HttpPost]
[Route(RouteConst.OAuth.AUTHORIZE)]
[FormValueRequired("submit.Accept")]
public async Task<IActionResult> Accept()
{
var request = this.HttpContext.GetOpenIdConnectRequest();
// Retrieve the profile of the logged in user.
var user = await this._userManager.GetUserAsync(this.User);
if (user == null)
{
return this.BadRequest(new ErrorUI
{
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = "An internal error has occurred"
});
}
// Create a new authentication ticket.
var ticket = await this.CreateTicketAsync(request, user);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return this.SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
Context
We are migrating a web application using Asp.Net Core (Web Api) to use an Angular2
Single Page Application (SPA) front end. The application was initially made with "Individual User Accounts". We have been converting the cshtml views to Angular2 components, the controller methods to produce Json, even incorporating JWT for authentication.
I have been using this Configuring Google authentication microsoft documentation.
Problem
The issue is illustrated by the following two controller methods. The LinkLogin method returns a ChallengeResult action. Obviously this cannot be converted to JSON. I presume this result is supposed to cause the browser to redirect to the external login provider, then automatically redirect to LinkLoginCallback.
[HttpPost]
public async Task<IActionResult> LinkLogin([FromBody] string provider)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(externalCookieScheme);
// Request a redirect to the external login provider to link a login for the current user
string redirectUrl = Url.Action(nameof(LinkLoginCallback), "Manage");
var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, userManager.GetUserId(User));
return Challenge(properties, provider);
}
[HttpPost]
[Produces("application/json")]
public async Task<ActionResult> LinkLoginCallback()
{
User user = await userManager.GetUserAsync(User);
if (user == null)
return Json(false);
ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync(await userManager.GetUserIdAsync(user));
if (info == null)
return Json(false);
IdentityResult result = await userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(externalCookieScheme);
return Json(true);
}
return Json(false);
}
If I do attempt to call LinkLogin from Angular I get this response:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:53462' is therefore not allowed access. The response had HTTP status code 405.
So I ended up coming up with a solution that avoids "ChallengeResult" by modifying the flow between Angular2 and the WebApi.
Added the angular2-social-login npm package to the angular app (following all their easy instructions for setup).
Merged both controller methods into one. The ExternalLoginViewModel parameter is a simple class with Email, ProviderKey and LoginProvider properties (populated in Angular in step 3):
[HttpPost]
[Produces("application/json")]
public async Task<ActionResult> LinkLogin([FromBody] ExternalLoginViewModel info)
{
if (ModelState.IsValid)
{
User user = await userManager.GetUserAsync(User);
if (user != null)
{
IdentityResult result = await userManager.AddLoginAsync(user, new UserLoginInfo(info.LoginProvider, info.ProviderKey, info.LoginProvider));
if (result.Succeeded)
return Json(true);
}
}
return Json(false);
}
Changed the Angular2 client to use the social media package to get the credentials needed for the web api. Here is my LinkProvider method demonstrating the connection: (this.service.LinkLogin calls the LinkLogin controller method)
public async LinkProvider(providerName: string): Promise<void>
{
const result: any = await this.auth.login(providerName).toPromise();
const success: boolean = await this.service.LinkLogin({
email: result.email,
loginProvider: result.provider,
providerKey: result.uid
});
// Handle link response here
}
I have inherited an existing application. This application uses ASP.NET MVC 3. It has some APIs. Those APIs look like the following:
[AcceptVerbs(HttpVerbs.Post)]
[Endpoint]
public ActionResult AuthenticatePlayer(string username, string password)
{
// Ensure that the user entered valid credentials
if (Membership.ValidateUser(username, password) == false)
return Json(new { statusCode = StatusCodes.INVALID_CREDENTIALS, message = "You entered an invalid username or password. Please try again." });
// Get the profile of the person that just logged in.
ProfileCommon userProfile = (ProfileCommon)(ProfileCommon.Create(username));
if (userProfile != null)
{
string name = username;
if (String.IsNullOrEmpty(userProfile.FirstName) == false)
name = userProfile.FirstName;
return Json(new {
statusCode = StatusCodes.SUCCESS,
payload = name,
username = username.ToLower(),
});
}
}
[AcceptVerbs(HttpVerbs.Get)]
[Endpoint]
public ActionResult SomeUserAction(string q)
{
// TODO: Ensure the user is authorized to perform this action via a token
// Do something
return Json(new { original = q, response = DateTime.UtcNow.Millisecond }, JsonRequestBehavior.AllowGet);
}
I'm trying to figure out how to integrate a token-based authorization schema into this process. From my understanding, a token-based system would return a short-lived token and a refresh token to a user if they successfully login. Then, each method can check to see if a user is authorized to perform the action by looking at the token. I'm trying to learn if this is built-in to ASP.NET MVC or if there is a library I can use. I need to figure out the shortest way to get this done.
Thank you so much!
I've built a WebAPI Token Authentication library a year ago, providing Token based authentication:
WebAPI Token Auth Bootstrap is out of the box Token based User Auth for WebAPI applications, Provides ready to use 'TokenAuthorize'
Attribute and 'TokenAuthApiController' Controller.
Among its features - Token Based User Authentication User Property inside the
TokenAuthApiController (Id, Username, Role, LastAccess).
Token Based User Authorization TokenAuthorizeAttribute with Access
Level - Public, User, Admin or Anonymous.
Built-in Functionality Login(), Logoff(), Error(), Unauthorized()
Responses with various overloads.
You can read more about here and in its own wiki in GitHub.
Nowadays I am working on a Node.js application and I am using Json Web Tokens (JWT) using Node.js library and it is very easy and straightforward.. its Node.js after all ;)
I saw there is a .NET implementation of JWT explained on this article which I recommend you to look at.
You can use Owin ... i.e. Microsoft.owin.security
I haven't tried this implementation but this is just to give you an idea:
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Json(new {
statusCode = StatusCodes.SUCCESS,
payload = name,
username = username.ToLower(),
accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket)
});