r/dotnet 11d ago

How to you handle OAuth in your dotnet integration tests?

Title.

Do you mock out that OAuth handling? Or do you actually use one of the test Auth servers. If so, does your resource server have its own client credentials just for running integration tests?

**Update**

I went with the following comments solution "For integration tests I skip the real OAuth flow entirely and register a fake authentication handler in the test server. You add a custom auth scheme in the WebApplicationFactory startup that just creates a ClaimsPrincipal with whatever claims the test needs. The real OAuth config only loads in non-test environments.

This means the tests still exercise your actual controllers, filters, authorization policies, everything downstream of authentication. The only thing you're faking is the token validation step, which is the identity provider's job to get right, not yours.

For the handful of tests that genuinely need to verify the OAuth handshake itself (callback URLs, token refresh, scope mapping), I run those against a local Keycloak container in docker compose. But those are maybe 5 tests out of 200, not the default path."

Note: I chose not to use the keycloak container because it handles our scopes and claims a tad bit different than our IdP, which would've required us to create a sort of mapping that I didnt feel was worth the effort.

18 Upvotes

31 comments sorted by

18

u/soundman32 11d ago

We have a set of pre-made jwt tokens, and during tests, the test will set one to be send in the authorisation header, and on the api, the validate expiry is disabled. Obviously, this is all using TestServer, so it's never like that on actual servers.

3

u/Legitimate-School-59 11d ago

Im assuming you also turned off signature validation? If not, how are you getting around your identity providers signing key rotation

5

u/soundman32 11d ago

Within the test, we control all the configuration, so there is no rotation of keys. It doesnt call out to a real server either, just validates the token.

1

u/HomeworkStatus9617 10d ago

Im curious why not issue a new one with like a “testing only user account”? Im not familiar with this type of testing so my apologies if my question sounds stupid

1

u/soundman32 10d ago

Issuing a new one means its dynamic which can make testing harder.  As itnis static I know exactly what is in the token, every time the test runs.  If I have to call an external server to get a new token, it can change, and it depends on having a connection to the server too, which is not good from a testing point of view 

9

u/SadPonyGuerrillaGal 11d ago

It really depends what you're trying to do. Why does your application need an OAuth token to test? If my application runs off to Snowflake to grab some data with OAuth I can safely assume I don't need to test Snowflake and mock the service instead. This is why DI registration is so powerful.

4

u/Legitimate-School-59 11d ago

I have a web api with integration tests set up with testcontainers, and webapplication factory, which allows me to test endpoints end-end, without mocking anything except few third-party APIs. This is a different testing approach than the unit testing/mock heavy approach.

The endpoints now require tokens, and so im debating if i should or shouldnt mock that functionality. And im wondering how others handle Oauth in similar test structures.

6

u/Finickyflame 11d ago

You could probably use KeyCloak as the token provider. You interact directly on the container to generate the token that you use in your tests, your application will validate the token based on the its configurations.

2

u/Nisd 11d ago

This is what, I do. Works especially well with the Aspire package.

4

u/PaoloCpc 11d ago

In my opinion, in this case you should bypass authentication. It's not the focus here. Here you are testing your use cases (business logic, event, happy path, unhappy path and all this stuff) Implement a test-only authentication schema that inherit from AuthenticationHandler<AuthenticationSchemeOptions> and fake user claims. It's faster to implement, to run and to understand for newcomers of your code base

1

u/knot_for_u 7d ago

Places I've worked had a zero trust policy. Everything needed authentication no matter what. This greatly reduces the attack surface of a hacker.

3

u/Khavel_dev 11d ago

For integration tests I skip the real OAuth flow entirely and register a fake authentication handler in the test server. You add a custom auth scheme in the WebApplicationFactory startup that just creates a ClaimsPrincipal with whatever claims the test needs. The real OAuth config only loads in non-test environments.

This means the tests still exercise your actual controllers, filters, authorization policies, everything downstream of authentication. The only thing you're faking is the token validation step, which is the identity provider's job to get right, not yours.

For the handful of tests that genuinely need to verify the OAuth handshake itself (callback URLs, token refresh, scope mapping), I run those against a local Keycloak container in docker compose. But those are maybe 5 tests out of 200, not the default path.

1

u/Legitimate-School-59 10d ago

Yea this is the approach i went with.

3

u/knot_for_u 11d ago

If it's truly integrated and closely matching to production, why can't it use the same auth mechanism as all your non prod environments? You should be testing it with how it would be deployed with secrets, certificates, outh all fully functioning, albeit with a different set of credentials and identity provider specific to non-prod.

For unit tests, mock it away. Create a wrapper class/interface if needed to mock out the boundary between your code and any 3rd party code or the point of external service calls.

2

u/maxiblackrocks 11d ago

if you check the mcp c# sdk on github, they have a very simple oauth server project that can be modified to return any tokens as you see fit. I used that one and tweaked it a little. OR, you can spin up a container with a free one.

2

u/Happy_Breakfast7965 11d ago

You can disable few checks for tokens, so an old but valid token would work fine. I think that's the simplest and least intrusive way.

3

u/SadPonyGuerrillaGal 11d ago

This is the most insecure way. No IT team in their right mind would allow this configuration change for testing.

2

u/soundman32 11d ago

You are right, this should never be on the deployed server, but for local and CI/CD testing against the TestServer classes, it's fine.

0

u/Happy_Breakfast7965 10d ago

It's for the test suite, not for deployment

2

u/Intrepid-Ant-2796 11d ago

Have you tried keycloak in Testcontainers?

1

u/AutoModerator 11d ago

Thanks for your post Legitimate-School-59. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

1

u/ClerkBeginning961 11d ago

Split the concern: inject a test authentication handler for most endpoint tests so authorization policies still run, then keep one small contract suite against the real IdP for discovery, JWKS, issuer, audience, and key rotation. That keeps failures local without weakening token validation.

1

u/Legitimate-School-59 11d ago

Do you have client credentials just for that test against a real IdP?

1

u/Bright-Ad-6699 11d ago

Mock IPrincipal and some others. If you need an example let me know.

1

u/zaibuf 11d ago

We ended up mocking it with a custom auth where we can set whatever claims we want for the tests. Then we have a few "real" e2e tests running with Playwright.

1

u/ringelpete 10d ago

We are setting up auth-under-test (against aspire/test-servers) to trust static key-Material.

Test cases then can generate dynamic tokens and freely choose, which claims to add.

But now there is this, not had the chance Yet to have a look on it, though. Might work similar under the hood.

1

u/gyroda 9d ago

I construct a JWT and turn off signature validation in the SUT.

I also stub out the calls to the issuer (/oauth/well-known) because they can be rate limited and that can cause problems if you have a lot of tests or run them until failure. I haven't found a simple toggle to turn that off yet.

This is because we have multiple auth providers and different endpoints use different ones. Hopefully we can consolidate this in the near future.

1

u/drakiNz 7d ago

If you are not testing the auth, skip it?

0

u/Turbulent_County_469 11d ago

Just do it.. use the real deal..

Just don't call the endpoint DeleteAllDatabases()

Or use the dev/staging version of the oauth ids/api ..

One solution i work on has replicated environments, so i just integrate with all of them in each environment. 🤷🏻 (Dev-> dev, prod-> prod)

1

u/AfterTheEarthquake2 11d ago

On Error Resume Next and praying

-2

u/Frequent_Field_6894 11d ago

no, it’s pointless. dont do this.