Openiddict. Unable to reach Accept(Deny) authorization endpoint for Authorization Code flow - c#

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);
}

Related

Angular and .Net Core - User isn't authenticated CLAIMS

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

How to completely log out of ASP.NET Core server application when using .NET Maui mobile application and Web Authenticator

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

Identity server 4 not removing cookie

I have front app on angular 5 and backend api on c# using identity server.
The problem is that when I click logout button, the token is removed and i am redirected to logout page.
But when I try to refresh main page, I am redirected to microsoftonline.com
authenticated automatically and redirected back to main page
I am missing providing username and password here, and this occurs in chrome incognito.
What I noticed is that if I remove manually the cookie from microsoftonline.com
and repeat the process, this time I will be asked for username and password.
So first I tried to clean all cookies this way but it din't help
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
bellow is my accountcontroller logout method and cookie screen
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
var subjectId = HttpContext.User.Identity.GetSubjectId();
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
{
if (model.LogoutId == null)
{
model.LogoutId = await interaction.CreateLogoutContextAsync();
}
try
{
await signInManager.SignOutAsync();
}
catch (NotSupportedException)
{
}
}
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await interaction.GetLogoutContextAsync(model.LogoutId);
var vm = new LoggedOutViewModel
{
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
await persistedGrantService.RemoveAllGrantsAsync(subjectId, "angular2client");
return View("LoggedOut", vm);
}
If I understand correctly you are federating to Microsoft from your IdentityServer4 service? If so when you sign out of your identity service you should also give the user the option to sign out of the external provider (if it supports the relevant feature - it'd need to define an end_session_endpoint in the discovery document).
This functionality is supported by the standard OIDC middleware so you should be able to initiate signout by calling SignoutAsync() and passing the name of the scheme for the MS federated sign in.
Another option is to always send prompt=login in your external sign in requests and then check the auth_time claim you get back. That way to you force interactive sign in always and also verify when it happened.
Try cleaning the cookies from the HttpContext itself, using the extension method, provided by Identity Server, like here.
Or try this:
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
in your Logout controller method.
3rd option (what I have in one of my test MVC clients is):
public ActionResult Logout()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
public void SignoutCleanup(string sid)
{
var cp = (ClaimsPrincipal)User;
var sidClaim = cp.FindFirst("sid");
if (sidClaim != null && sidClaim.Value == sid)
{
Request.GetOwinContext().Authentication.SignOut("Cookies");
}
}
Where the Logout method is called on the button click, and the SignoutCleanup is the one that is passed to Identity Server, when registering the client as a Client.BackChannelLogoutUri (or Client.FrontChannelLogoutUri, or both, depending on your scenario).
PS: Now, in general I think that your approach is not right, but I don't know your full case, so I'm not judging you - just giving and advice.
For front-end clients (Angular, Vue, vanilla JS etc..) it is recommended to use the client-side oidc-client-js library. And here is the usage example. As I said - this is just an advice, but if you are in the very beginning of your authentication setup, I would recommend you to have a look.

How can WebApi ChallengeResult be incorporated into an Angular2 SPA?

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
}

Token-based Authorization in Existing ASP.NET MVC App

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)
});

Categories

Resources