oauth/check_token does not check for roles/scopes associated with endpoint - security

I have one Authorization server and one resource server. I am creating access token at authorization server and try to use it at Resource server using RemoteTokenServices in oauth2 which hits '/oauth/check_token' internally to authorization server, where it only checks for token existence and its expiry. But it does not check for roles/scopes against endpoint given vs roles/scopes against access_token.
#FrameworkEndpoint
public class CheckTokenEndpoint {
#RequestMapping(value = "/oauth/check_token")
#ResponseBody
public Map<String, ?> checkToken(#RequestParam("token") String value) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}
if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, ?> response = accessTokenConverter.convertAccessToken(token, authentication);
return response;
}
}
Above code snippet is from CheckTokenEndpoint.java.
Is there any way to achieve roles/scopes based authorization also?

If anyone else come across a similar issue with JWT token implementation using XML-based configuration, I have solved it the following way
Oh and my detailed post on how to implement Spring OAuth2 using XML-based configuration can be found here
Some assumptions
You are using JWT tokens that have custom claims
You have provided a custom implementation of JwtAccessTokenConvertor which in turn implements TokenEnhancer interface (feel free to implement AccessTokenConvertor & TokenEnhancer interfaces without having to use JwtAccessTokenConvertor)
You are using XML-based configuration
A closer look at the CheckTokenEndpoint source code reveals the follow
private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
And looking at the source code of DefaultAccessTokenConvertor, it is the default implementation of AccessTokenConvertor interface which basically have the following contracts
Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
OAuth2Authentication extractAuthentication(Map<String, ?> map);
In my case, I used JWT tokens, meaning that the token value that I pass to the /oauth/token_check endpoint is a signed (with RSA keypair) JWT and the TokenCheckEndpoint will do a few checks such as
Checks if the token is in the db (oauth_access_token table), this does not apply to JWT implementation as they are not necessarily stored in db
Check that its valid JWT token in the first place
Check that the signature of the token is correct and it has not be tampered
Check that its not expired
Other checks that I don't know of
In addition to the above, I needed to check that the custom claim such as scope (i.e. basically role and its associated permissions) is same in the database (making sure that roles didn't change since token was issued).
Based on my debugging, when the /oauth/check_token endpoint is hit, the extractAccessToken followed by extractAuthentication methods is called respectively (at least with JWT implementation).
Since I have extended JwtAccessTokenConvertor (which in turn implements AccessTokenConvertor & TokenEnhancer interfaces) in order to enhance my JWT token to add custom claims (i.e. scope) to it by overriding the enhance method as shown below
#Component
public class MyJwtAccessTokenConvertor extends JwtAccessTokenConverter {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
//enhance the token with custom claims (i.e. user role scope)
//then return it
return result;
}
#Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
OAuth2AccessToken mytoken = tokenConverter.extractAccessToken(value, map);
/* validate the custom claims of token i.e. user role scopes
* and if any issue throw an exception
*/
return token;
}
}
I could easily validate that the JWT access token has the required user role scopes in the extractAccessToken method. If I detect any violation then I throw InvalidTokenException (can be custom exception too).

Related

Invalid Signature when generate bearer token

I am new to OAuth and I used this tutorial to generate access token from client app to target app. The code itself is working fine, but the access token I generated has invalid signature when I decoded on https://jwt.io/
Here's the code from the tutorial
public class ServicePrincipal
{
/// <summary>
/// The variables below are standard Azure AD terms from our various samples
/// We set these in the Azure Portal for this app for security and to make it easy to change (you can reuse this code in other apps this way)
/// You can name each of these what you want as long as you keep all of this straight
/// </summary>
static string authority = ""; // the AD Authority used for login. For example: https://login.microsoftonline.com/myadnamehere.onmicrosoft.com
static string clientId = ""; // client app's client id
static string clientSecret = ""; // client app's secret key
static string resource = ""; // target app's App ID URL
/// <summary>
/// wrapper that passes the above variables
/// </summary>
/// <returns></returns>
static public async Task<AuthenticationResult> GetS2SAccessTokenForProdMSAAsync()
{
return await GetS2SAccessToken(authority, resource, clientId, clientSecret);
}
static async Task<AuthenticationResult> GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext context = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = await context.AcquireTokenAsync(
resource, // the resource (app) we are going to access with the token
clientCredential); // the client credentials
return authenticationResult;
}
}
There is another piece of code I found that can also generate the access token:
AuthenticationContext authenticationContext =
new AuthenticationContext({authority});
ClientCredential clientCredential = new ClientCredential({client app id}, {client app secret});
try
{
AuthenticationResult result =
await authenticationContext.AcquireTokenAsync({target app's App ID URL},
clientCredential);
}
catch (Exception e)
{
return false;
}
Both of the code gave me invalid signature access token with version 1.0
There are two issues here:
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
Why would it be invalid signature?
I tried the same code with yours, got the same situation invalid signature, but when I changed the jwt ALGORITHM to HS256, I got the Signature Verified.
And the signature:
And the differences about RRS256 and HS256:
RS256 (RSA Signature with SHA-256) is an asymmetric algorithm, and it uses a public/private key pair: the identity provider has a private (secret) key used to generate the signature, and the consumer of the JWT gets a public key to validate the signature. Since the public key, as opposed to the private key, doesn't need to be kept secured, most identity providers make it easily available for consumers to obtain and use (usually through a metadata URL).
HS256 (HMAC with SHA-256), on the other hand, is a symmetric algorithm, with only one (secret) key that is shared between the two parties. Since the same key is used both to generate the signature and to validate it, care must be taken to ensure that the key is not compromised.
Are you posting your key into the form at jwt.io? Try to make a real rest call using the token in the authorization header. If everything is working and jwt isn't, maybe it's on them.
I noticed is that when I decode the access token, it shows "ver": "1.0". Does it mean it is using OAuth1.0? Because I suppose to use OAuth 2.0.. Why would the code generate token that create OAuth1.0 not OAuth2.0?
You are using OAuth2.0 , the ver:"1.0" means the JWT token is issued by Azure AD V1.0 Endpoint .
Why would it be invalid signature?
The API needs to check if the algorithm, as specified by the JWT header (property alg), matches the one expected by the API . AAD use RS256 , so you should change to RS256:
The normal way is to build from modulus and exponent , finding them from https://login.microsoftonline.com/common/discovery/keys matching kid and x5t from the token . Any use online tool like https://play.golang.org/ to get public key consists of two components: n and e. You can also use x5c value , click here for samples .

The provided anti-forgery token was meant for a different claims-based user than the current user - Token Authentication

my current Authentication process looks like this. I have a Auth API that generates a token with UseOAuthBearerAuthentication. Once I generate the token inside GrantResourceOwnerCredentials I set the Identity.Name by identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName));
I want to store my token in a HttpOnly cookie, so in TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
I saved it to a cookie, but I also need to prevent XSRF so I generate a XSRF token there as well.
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
If I look at the current Identity context from OAuthTokenEndpointResponseContext context, Identity.Name is set, so it should be using this name for the XSRF token generation. But HttpContext.Current.User.Identity is null In my both my APIs I also set
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
Now In my Resource API, I've already taken care of reading the auth token from the cookie and setting it to the Authorization header. That works fine. I have then created my own XSRF Attribute the verify the XSRF token.
public class ValidateAntiForgery : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("X-XSRF-TOKEN", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
catch(Exception e)
{
actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad Request");
}
}
}
Checking the Identity in actionContext, everything is set and the user is authenticated. However, AntiForgery.Validate(cookieToken, formToken); throws an exception as shown below. I've looked at the many other examples, but I can't find a solution.
System.Web.Mvc.HttpAntiForgeryException: 'The provided anti-forgery
token was meant for a different claims-based user than the current
user.'
EDIT: So it seems where I generate my XSRF token in TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
context.Identity is set with the authenticated User, however HttpContext.Current.User is null. Even though the XSRF tokens generated here are technically valid, I think they are using null from the HttpContext. If I generate the XSRF token is a separate GET request with [Authorize] so that HttpContext.Current.User is not null, then the AntiForgery.Validate works fine.
I want the XSRF token to be returned with the Authentication token, but I'm not sure how to do that. How do I set HttpContext.Current.User.Identity?
EDIT 2: So I was able to fix the problem using a hacky way. When I want to generate the XSRF token I call the following function.
[Authorize]
public string generateXSRFToken(ClaimsIdentity identity)
{
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, new string[0]);
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
It works!, but it's ugly, I would like a more elegant way.
This should fix your problem:
var returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl.IsEmpty()) {
// Some external login providers always require a return URL value
returnUrl = Href("~/");
}
if (WebSecurity.Login(email, password, rememberMe))
{
Context.RedirectLocal(returnUrl);
return;
}
else
{
ModelState.AddFormError("The user name or password provided is incorrect.");
}

External Login WebAPI2 MVC5

I need to use a 3rd party token as a way to authenticate. I'm getting the token in the home controller and need to be able to send it on to my WebAPI controllers (It's an SPA application using Backbone). Is there a way of doing this?
EDIT:
Relevant code:
public ActionResult Index(string projectId, int companyId, bool isCompanyAdmin)
{
// if not a valid user return error
var validate = new Validate().ValidateContext(HttpContext,
"Key", "Secret");
if (!validate.IsValidated) return View(Constants.ValidationFailed);
// The info validated, so now I can set it to authorized
// put code here for doing it
//Get the model for the user
try
{
var model = ConvertToVM(_smsRepository.GetCompany(companyId, projectId));
}
catch (ProviderIncompatibleException)
{
// connection string wrong
return View(Constants.ConnectionFailed);
}
catch (Exception e)
{
// catch all
return View(Constants.DatabaseError);
}
//create and send through the view model that determines what view the user will get
return View(model);
}
Ok I put in the index method on the Home Controller. Like I said, we make a call to a third party API passing in the context, the client key, and the client secret to verify our identity. Could I just add a Bearer token in the home controller? Or otherwise pass the http context to OWiN and use some custom logic to add the token if validate.IsValidated is true? It needs to be something that works with WebAPI.

context.Request.User is null in OWIN OAuthAuthorizationServerProvider

I'm trying to implement OAuth using OWIN for a Web API v2 endpoint on my local intranet. The API is hosted in IIS using built-in Windows Authentication. In short, this is what I want to happen.
When I ask for my Token at /token
Pull the WindowsPrincipal out of the OWIN context
Use the SID from the WindowsPrincipal to look up some roles for this
user in a SQL table.
Create a new ClaimsIdentity that stores the username and roles
Turn that into a Json Web Token (JWT) that I sent bak
When I request a resource from my API using my token
Convert the JWT Bearer token back to the ClaimsIdentity
Use that ClaimsIdentity for authorizing requests to the resource by
role
This way I don't have to do a database lookup for user roles on each
request. It's just baked into the JWT.
I think I'm setting everything up correctly. My Startup.Configuration method looks like this.
public void Configuration(IAppBuilder app)
{
// token generation
// This is what drives the action when a client connects to the /token route
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
// for demo purposes
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
AccessTokenFormat = GetMyJwtTokenFormat(),
Provider = new MyAuthorizationServerProvider()
});
//// token consumption
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions()
{
Realm = "http://www.ccl.org",
Provider = new OAuthBearerAuthenticationProvider(),
AccessTokenFormat = GetMyJwtTokenFormat()
}
);
app.UseWebApi(WebApiConfig.Register());
}
MyAuthorizationServerProvider looks like this...
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Since I'm hosting in IIS with Windows Auth enabled
// I'm expecting my WindowsPrincipal to be here, but it's null :(
var windowsPrincipal = context.OwinContext.Request.User.Identity;
// windowsPrincipal is null here. Why?
// Call SQL to get roles for this user
// create the identity with the roles
var id = new ClaimsIdentity(stuff, more stuff);
context.Validated(id);
}
}
My problem is that context.Request.User is null here. I can't get to my WindowsPrincipal. If I create some other dummy middleware, I can get to the WindowsPrincipal without issue. Why is it null in this context? Am I doing something wrong?
Swap the order of UseOAuthAuthorizationServer and UseOAuthBearerAuthentication. UseOAuthBearerAuthentication calls UseStageMarker(PipelineStage.Authenticate); to make it (and everything before it) run earlier in the ASP.NET pipeline. User is null when you run during the Authenticate stage.

Validate a token signature for subsequent request in a restful web api

When the user is authenticated I put a signed token in the response authorization header.
Every furthere access on a ressource url is only allowed with a valid signed token.
When I create the token and valdiate it:
var principal = tokenHandler.ValidateToken(tokenString, validationParameters);
then I get the principal (user who made the request) when the signed key is the same which got
used by creating the token.
That I can use the same signed key after authentication and during the ressource request to validate the token I have created this class:
public static class ApiConstants
{
private static readonly RNGCryptoServiceProvider CryptoProvider = new RNGCryptoServiceProvider(new byte[33]);
private static byte[] key = new byte[32];
static ApiConstants()
{
CryptoProvider.GetBytes(key);
}
public static byte[] GetSignedKey()
{
return key;
}
}
Is there anything wrong that I put this code in a static class which is actually my full purpose as I want the filling up of the byte array with random numbers to happen only one time!?
Is there still something I can improve?
You Can't make the signature token as static. because then it will become as global variable and shared by all request(thread). Also you will face concurrency issue with static field.
If you want to make it session specific. then you need to store in a session variable not in a static field.

Resources