Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get the user claim populated so that standard attributes can be used #20

Closed
scott-david-walker opened this issue Jun 10, 2023 · 12 comments

Comments

@scott-david-walker
Copy link

Hi,

I'm wondering if you have any documentation on the correct way of validating the authentication of a user using the standard .NET way (E.G. via Authorize attributes].

I've tried numerous implementations with the "AddJwtBearer" all with no success.

What I'd expect is to be able to do something like this:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.Authority = "https://whatever-the-issuer-is.login.authress.io";
});

and have this auto populate the identity so that I can use the attributes as normal.

Any advice on this would be welcome.

Cheers

@wparad
Copy link
Member

wparad commented Jun 10, 2023

We can definitely got some documentation going for the specific use case. I would like to ask two things:

  1. Do you have a minimal reproduction c# project of your implementation that we can validate?
  2. What error or problem are you seeing. Does the builder throw an error, are their missing attributes that you would expect sourced from the JWT bearer? Something else?

Assuming the authentication actually does work, if you are passing along the access token, there is only going to be the sub and aud, I don't recall that there is anything else of merit in there. Is there?

@scott-david-walker
Copy link
Author

scott-david-walker commented Jun 10, 2023

I can definitely throw a sample app together to explain what I'm expecting.

I'm currently logging in via a react application and then making a request to the back end API. Nothing really interesting in the claims, only iss, clientid, exp, azp, sub etc

There's no error thrown, only getting a unauthorized result when we should be getting authorised.

@scott-david-walker
Copy link
Author

As requested, here is a simple example https://github.com/scott-david-walker/authress-example/

AddJwtBearer has the configuration in program.cs. The WeatherForecast controller has an authorize attribute and I've added a dummy UserMiddleware class. By the time the UserMiddleware class is executed, User and Claims should be populated correctly.

There's a fair chance that I'm missing something really simple here but I'm not seeing it. This setup works with Auth0 and Azure B2C so I'm not sure what I'm doing wrong. Any further questions then please shout.

@wparad
Copy link
Member

wparad commented Jun 12, 2023

Thanks for the reproduction that definitely helped narrow the issue down. First I want to start with some context:

Context

Authress uses the more advanced and modern EdDSA signature with ed25519 keys. Think of this as the v3.0 of RSA. Azure B2C, Cognito, and Auth0 use RS259 with RSA keys (hopefully 2048 bit ones or higher, but I haven't looked into it). Since EdDSA signature is based on ECC, it's believed generally to be more secure. ECC is the preferred signature mechanism for OAuth, everyone should be using it. Some sources will suggest it is the same security but with shorter keys and shorter signatures. So in any case, a better pick.

However, this a relatively new concept in the last half a decade, and so there are still a lot of technologies out there that are so slow to implement this strategy which is unfortunate. The good news is, it almost all languages we have created a token verifier that implements the necessary logic to be used, so this isn't a problem. In C# the language implementation is managed by Azure, which is behind.

Action Plan

We will take a multistage approach here to fixing C#:

  1. [Short Term Solution] We've temporarily disabled EdDSA signatures for your authentication. Instead Authress will maintain an RS512 (>> RS256) signature strategy for your account. This is still incredibly secure. And works according to the C# documentation for the System.IdentityModel.Tokens.Jwt: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L71 this means on your next login, you should get an RS512 signed token instead of the EdDSA one and then the aspnet framework will have no problem parsing the claims. I'm expecting this to be the only problem here, but you know how software goes. (I.e. which versions of .Net and library, are also relevant).
  2. [Interim Solution] We are going to create a custom implementation to work here and update our documentation to avoid others running into tun in the future.
  3. [Long term] we'll file a PR with correct implementation in the MS C# Core security library so that this actually works correctly. Someone has to do it, and it seems like we might be the right team to do this. Once this is updated, there could be a discussion down the line on migrating to the new version, but that's too far away to warrant a discussion now.

Conclusion

So since this switch has already been made in your account (we have some admin tool automation for just these sorts of cases) a simple retry in theory should work on your side. I.e. just log in again with your react app, and we can deal with additional issues that come up at that time.

I personally really like this solution, it feels clean and easy, rather than additional complexity. However, if you would prefer to go down a different path, that's also doable.

FWIW we also have our community at Authress Community if there are additional non library implementation issues.

@scott-david-walker
Copy link
Author

scott-david-walker commented Jun 12, 2023

First of all, Thanks for the extremely elaborate explanation.

Unfortunately, I still appear to be having an issue with the simple weather forecast application auth.

I've thrown the jwt into "jwt.io" and found that it classes the algorithm used as "EdDSA". Could you confirm the account that you've associated this change to? I think I may have multiple with the same email address but different providers.

I've tried all three of the accounts I created and none of them appear to be returning an RSA algorithm.

@wparad
Copy link
Member

wparad commented Jun 12, 2023

First of all, Thanks for the extremely elaborate explanation.

Unfortunately, I still appear to be having an issue with the simple weather forecast application auth.

I've thrown the jwt into "jwt.io" and found that it classes the algorithm used as "EdDSA". Could you confirm the account that you've associated this change to? I think I may have multiple with the same email address but different providers.

I've tried all three of the accounts I created and none of them appear to be returning an RSA algorithm.

Awesome, could you send us an email at our support with the accountIds you plan on using going forward. The account ID is listed on the General Settings page of the Management Portal.

It's fine if you want all of them changed, but I don't want to expose account IDs on GitHub. They aren't super sensitive, but we try to avoid unnecessary data exposure if we can help it.

For the record the one we changed was attached to a Google account, the Authress accountId strating with acc_b9i...

@scott-david-walker
Copy link
Author

scott-david-walker commented Jun 12, 2023

That account is the correct account, I plan on removing the others soon.
Unfortunately, when using this account, the token that has been returned is still an "EdDSA" signature.

{
     "alg": "EdSDA", 
    "kid": "kid-here",
    "type" : "at+jwt" 
}

I can also send a support email and continue this conversation there if preferable :-)

@wparad
Copy link
Member

wparad commented Jun 12, 2023

Hey, just kicked it, I'm pretty sure it was still in a cache somewhere, I'll have someone investigate that later though. I just tried this using the Management Portal "Test Connection" button, and got the desired result. Would you be able to try verifying again. I'm sorry for all the extra cycles on this :(.

@scott-david-walker
Copy link
Author

That did the trick and now it works perfectly!!!!!
No need to apologise, from my end this has been a really nice experience!
Can't wait to implement machine to machine next and finally move away from Azure b2c

Thanks!!

@wparad
Copy link
Member

wparad commented Jun 12, 2023

👍 I assume there is going to be a similar dance when it comes to the M2M tokens, depending on whether or not you use the ServiceClientTokenProvider. The one that comes with library also creates EdDSA tokens. However if you call the oauth token endpoint (it's in the API documentation) with the client credentials, I would expect you to get back an RS512 signed token for that service client. Of course with future update to the libraries that can be avoided. I guess just keep an eye out for this when you are testing. But if it isn't obvious and it doesn't seem like the right thing is happening feel free to re-open this ticket:

@scott-david-walker
Copy link
Author

My intention would be to provide each consumer of the API a machine to machine credential. So that they can use an OAuth flow rather than an api key. Would the link you provide be the correct approach for this?

@wparad
Copy link
Member

wparad commented Jun 12, 2023

My intention would be to provide each consumer of the API a machine to machine credential. So that they can use an OAuth flow rather than an api key. Would the link you provide be the correct approach for this?

By API, I'm assuming you mean your service's API. Then Yes. Normally, service clients could use the ServiceClientTokenProvider but that won't work due for the same reason the EdDSA tokens don't work out of the box. There are work arounds, but it probably makes more sense to be consistent, so using the OAuth Token endpoint is the best solution here. I assume something is going to make me take that back, I haven't looked at the OAuth interface API in a while, but we have a general mentality of "if it seems like it could work, it should work". Because making things work by default is a much better experience over complex explanations.

So if it doesn't or it isn't obvious how to do that, we can take that discussion to an email, and we'll get it fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants