Add id_token as claim AspNetCore OpenIdConnect middleware - claims-based-identity

I am trying to set IdTokenHint when sending the sign out request. In the previous Microsoft.Owin.Security.OpenIdConnect middleware I would be able to set the id_token as a claim in the SecurityTokenValidated method using the SecurityTokenValidated notification by doing something like this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
Notifications = new OpenIdConnectAuthenticationNotifications
{
//Perform claims transformation
SecurityTokenValidated = async notification =>
{
...
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
},
RedirectToIdentityProvider = async n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
}
With the new middleware Microsoft.AspNetCore.Authentication.OpenIdConnect (in ASP.NET Core RC2) I am having trouble trying to accomplish the same thing. I am assuming I should tap into the Events like so.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
Events = new OpenIdConnectEvents()
{
OnTokenValidated = context =>
{
...
context.SecurityToken.Payload.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
},
OnRedirectToIdentityProviderForSignOut = context =>
{
var idTokenHint = context.HttpContext.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
The problem I'm seeing is that the claims do not remain on the SecurityToken and don't get set on the HttpContext.User. What am I missing?

Regarding your code above, at least in version 2.1 of ASP.NET Core, the ID token can be accessed via context.Properties.GetTokenValue(...) (rather than as a user claim).
And, as Brock Allen said in a comment to your question, the OpenIdConnectHandler will automatically include the idTokenHint on sign out. However, and this bit me for a few hours today, when the handler processes the sign-in callback, it will only save the tokens for later if OpenIdConnectOptions.SaveTokens is set to true. The default is false, i.e., the tokens are no longer available when you do the sign-out.
So, if SaveTokens is true, the handler will automatically include the idTokenHint on logout, and you can also manually access the id token via context.Properties.GetTokenValue(...).

Related

ASP.NET Core 3.1 - AddJwtBearer, OnTokenValidated event not respecting custom message on Fail

I have a scenario where I need to recreate the principal if a bearer token is provided in the request. For this I use the OnTokenValidated event to execute some custom logic (if bearer is valid). I check if the user email is verified, if so I add custom claims to the user identity which I can then access later on during the same request and make use of the authorisation attributes on controllers and actions.
However I'm trying to return a custom message if the email is not verified, but I keep getting "Unauthorised" back, even though this code is being hit and using the preferred message.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = customDomain;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudiences = audiences,
ValidateIssuer = true,
ValidIssuers = issuers
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = async (context) =>
{
var user = context.Principal;
//Check if already restored during current request
if (user.GetDefaultUserPrincipal() == null)
{
var securityManager = context.HttpContext.RequestServices.GetRequiredService<ISecurityManager>();
var authResponse = await securityManager.AuthenticateMarketplaceFromBearerRequestAsync(user);
if(!authResponse.IsAuthenticated)
{
context.Fail(authResponse.Message);
}
}
}
};
});
Am I missing something here? I've also tried throwing an exception and handling that response in the AuthenticationFailed event, but I get the same thing.
Alternatively I'm playing with the idea of creating a custom policy to do this check as long I can still return a custom response message.
In order to display your custom message when failing the authentication, you can write it to the response of the AuthenticationFailedContext context object.
...
if (!authResponse.IsAuthenticated){
// set the content-type of the response
context.Response.ContentType = "application/json";
// prepare your custom data
var data = new { MyCustomMessage = authResponse.Message };
// user serializer to form your data to string. Here I used Newtonsoft.Json
var jsonResult = JsonConvert.SerializeObject(data);
// Write the jsonresult to the response. Make sure this returns a Task
context.Response.WriteAsync(jsonResult);
}
...

IsAuthenticated is always false in Custom Authorization Attribute (.NET Core 2.2 and JSON Web Token)

I'm trying to build my own custom authorization attribute using JSON Web Token JWT in .net core 2.2.
I'm calling the Authorized API using Postman and I'm facing two problems here:
The Claims in the JWT sent are not being received
IsAuthenticated property is always false in User.Identity.IsAuthenticated.
Please note that the part of JWT is working totally fine, a JWT is being created as I want with the correct Claims and I've checked it on https://jwt.io.
As for my Startup.cs I'm using app.UseAuthentication()
Here's how I'm adding the JWTAuthentication to services:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x=>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = false
};
});
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
And here's a snippet of MyCustomAuthorizationAttribute.cs
public string Permissions { get; set; } //Permission string to get from controller
public void OnAuthorization(AuthorizationFilterContext context)
{
//Validate if any permissions are passed when using attribute at controller or action level
if (string.IsNullOrEmpty(Permissions))
{
//Validation cannot take place without any permissions so returning unauthorized
context.Result = new UnauthorizedResult();
return;
}
//The below line can be used if you are reading permissions from token
var permissionsFromToken = context.HttpContext.User.Claims.Where(x => x.Type == "Permissions").Select(x => x.Value).ToList();
var requiredPermissions = Permissions.Split(','); //Multiple permissiosn can be received from controller, delimiter "," is used to get individual values
foreach (var x in requiredPermissions)
{
if (permissionsFromToken.Contains(x))
return; //User Authorized. Wihtout setting any result value and just returning is sufficent for authorizing user
}
context.Result = new UnauthorizedResult();
return;
}
Note: I know that this question is asked a lot before, but I tried most of them and nothing worked for me.
I found out that the order of putting the middleware services in Startup.cs matters. As we can see in the code snippet above. I'm using AddIdentity() middleware after using AddAuthentication() and AddJwtBearer() which was somehow removing the JWT authentication provider.
The solution was simply to put AddIdentity() middleware with all of its sub-methods before AddAuthentication() and AddJWTBearer() middlware.

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

Redirecting Controller through OWIN startup

I am not a .net developer and got a task to apply SSO in an existing .net application.
I have added OWIN OpenIDConnect for SSO. My project is not MVC project. Now my call is going through Startup.cs at the iisexpress server startup time. But my login page (login.aspx) is being called with /login.aspx.
I want to call startup when I call submit button on login page. The call is not going through startup.
My ConfigureAuth method is
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(Microsoft.Owin.Security.Cookies.CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login.ashx")
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = "Authority",
ClientId = "ClientId",
RequireHttpsMetadata = false,
ClientSecret = "ClientSecret",
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
AuthenticationMode = AuthenticationMode.Active,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
//I'm able to break into this method
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
}
});
System.Console.WriteLine("After OpenIdconfiguration");
}
Please suggest how can I redirect the Controller call through startup class.
Thank you
The short answer is that most of the code will not be executed directly. These are events that are executed as part of the authentication process.
The longer answer is that you need to fill in the blanks on what needs to actually happen for each of these events. A little bit of information on OWIN might help.
I think of OWIN as a pipeline that leads to your application. The request goes through the pipeline before it reaches your application. When your application sends a response, this pipeline is used again. Along the way, there are components called "middleware" that can act on the request and on the response. Using the pipeline model, think of the middleware as valves in the pipeline. The middleware is executed in order on the way in, and in reverse order on the way out.
So back to your code. This line:
app.UseOpenIdConnectAuthentication(
Tells OWIN to use OpenIdConnect for authentication. You need to write the code for each of the events that are part of the OpenIdConnect process. The first one RedirectToIdentityProvider needs to redirect (probably with an http 302) to your identity provider, which could be something like ForgeRock or PingIdentity or IdentityServer. Each of those events needs the code written for them.
The whole topic of OpenIdConnect is a fairly broad and is more than a little complex, probably more than what I could realistically do inside an answer. But there are plenty of resources on the topic.
I hope that helps

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook