Git Product home page Git Product logo

duo_universal_csharp's Introduction

Duo Universal Prompt C# Client

Build Status Issues Forks Stars License

This library allows a web developer to quickly add Duo's interactive, self-service, two-factor authentication to any .NET web login form.

See our developer documentation at https://www.duosecurity.com/docs/duoweb for guidance on integrating Duo 2FA into your web application.

What's here:

  • DuoUniversal - The Duo SDK for interacting with the Duo Universal Prompt
  • DuoUniversal.Example - An example web application with Duo integrated

The example application has a dedicated README with further instructions on how to build and run it.

Usage

This library requires .NET Core 3.1 or higher, or .NET Framework 4.7.1 or higher

The library is available on NuGet at https://www.nuget.org/packages/DuoUniversal/1.2.2

Include it in your .NET project with:

dotnet add package DuoUniversal --version 1.2.2

TLS 1.2 and 1.3 Support

Duo_universal_csharp uses the .NET libraries for TLS operations. .NET 4.7 or later is required for TLS 1.2; .NET 4.8 or later is required for TLS 1.3.

Building with the .NET CLI

Run dotnet build to generate the assemblies.

Testing

The tests require .NET Core 6.0.

With the .NET CLI

From the root directory run:

dotnet test

Linting

With the .NET CLI

Check the code format with:

dotnet format --verify-no-changes

Support

Please report any bugs, feature requests, or issues to us directly at [email protected].

Thank you for using Duo!

https://duo.com/

duo_universal_csharp's People

Contributors

aaronatduo avatar dependabot[bot] avatar mbish avatar yevgenkre avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

duo_universal_csharp's Issues

Reading certificates file broken on Windows

CertificatePinnerFactory::ReadCertsFromFile tries to split the contents of ca_certs.pem using Environment.NewLine. Windows uses CRLF line endings (the value of Environment.NewLine is "\r\n") but the .pem file uses LF line endings ("\n").

var twoNewlines = $"{Environment.NewLine}{Environment.NewLine}";
return certs.Split(new string[] { twoNewlines }, int.MaxValue, StringSplitOptions.None);

This results in the certs being parsed improperly, and causes an AuthenticationException when trying to exchange the authorization code for the 2FA result.

Changing the line ending type of ca_certs.pem to CRLF from LF fixes the issue on Windows, but probably breaks on Linux. Alternatively, changing the value of twoNewLines to "\n\n" while keeping the line ending type of ca_certs.pem as LF also works.

Can you switch to splitting the new lines in a platform-neutral way? Maybe just split on a specific character(s) instead of new lines?

Cors error

Description

Getting cors error while using duo universal prompt using .net core with react

===========

return new RedirectResult(prontuari);

==========

after this line we are getting cors error in .net core

JSON decode error parsing `is_encryption_enabled`

When running the example app on my Mac (either using native dotnet for Mac, or docker), after completing the 2FA, I receive an internal server error:

An unhandled exception occurred while processing the request.
InvalidOperationException: Cannot get the value of a token type 'True' as a string.
System.Text.Json.Utf8JsonReader.GetString()

JsonException: The JSON value could not be converted to System.String. Path: $.access_device.is_encryption_enabled | LineNumber: 0 | BytePositionInLine: 646.

Digging into the code and server's response, it seems this client is expecting a String value for is_encryption_enabled, but the server is returning the boolean true, causing the Deserialization error and exception.

This same issue also like affects is_firewall_enabled and is_password_set

Snippet of JSON from the server response:

"os":"Mac OS X","os_version":"12.3.1","browser":"Chrome","browser_version":"100.0.4896.75","flash_version":"uninstalled","java_version":"uninstalled","is_encryption_enabled":true,"is_firewall_enabled":true,"is_password_set":true},

Intermittent API call failures due to no leeway in nbf field of JWT

Symptom

Calls to Client.DoHealthCheck() and Client.ExchangeAuthorizationCodeFor2faResult() intermittently (but frequently) fail, with the returned exception indicating that the Duo API has returned a 400 Bad Request response code.

Cause

When the client library sends JWTs to the Duo API functions (oauth/v1/health_check and oauth/v1/token), it allows Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler to set the nbf field of the JWT to its default value, which is the current time. When the Duo API receives the JWT, it does not allow any leeway in validating the nbf time (though it could, according to RFC 7519). If the client computer's clock is a second or two ahead of the Duo API server's clock, the API call will fail.

I've verified this experimentally: sending a JWT with nbf=2 seconds in the past, the API calls always succeed. With nbf=2 seconds in the future, they always fail. In between, the call may succeed or fail depending on the exact difference between the two servers' clocks, and the transit time of the request.

Suggested fix

Since the API allows no leeway on nbf, change JwtUtils.cs to explicitly add an nbf field to the token with a few minutes' leeway built in.

Replace

        private const int FIVE_MINUTES = 5;

with

        private const int TOKEN_NOTBEFORE_OFFSET_MINUTES = -5;
        private const int TOKEN_EXPIRY_OFFSET_MINUTES = 5;

In SignPayload(), replace this:

            JsonWebTokenHandler jwtHandler = new JsonWebTokenHandler
            {
                TokenLifetimeInMinutes = FIVE_MINUTES
            };

            // CreateToken automatically adds "iat","nbf", and "exp" to the payload
            // "exp" is calulcated using the TokenLifetimeInMinutes set above

with:

            JsonWebTokenHandler jwtHandler = new JsonWebTokenHandler();

In GenerateParams(), explicitly add the iat, nbf and exp claims, before the return claims;:

            DateTime now = DateTime.UtcNow;
            claims.Add(Labels.IAT, EpochTime.GetIntDate(now));
            claims.Add(Labels.NBF, EpochTime.GetIntDate(now.AddMinutes(TOKEN_NOTBEFORE_OFFSET_MINUTES)));
            claims.Add(Labels.EXP, EpochTime.GetIntDate(now.AddMinutes(TOKEN_EXPIRY_OFFSET_MINUTES)));

Dictionary<string, string> and IDictionary<string, string> will need to be changed to Dictionary<string, object> and IDictionary<string, object> in GenerateParams(), GeneratePayload() and SerializeParams(), so that the integer values of the iat, nbf and exp claims can be added.

Unable to run Sample

Hi I am unable to run the sample here is the error i get, can somebody please help
{
"error": "unauthorized_request",
"error_description": "Unauthorized OIDC request"
}

Failed Health Check Responses Deserialize to null

Was running this locally today, and was debugging why my calls were not working. The health check was failing, but while I could verify the response message was FAIL (as opposed to OK), the response detail object is not being correctly deserialized.

DoPost<T> tries to deserialize the result to an instance of T, but while I get a response detail object back:

image

The actual C# object is just set to null.

It's likely just a case of the properties being returned not matching the properties of the response object itself, but upon failure this should be deserialized appropriately.

Add documentation on how to call from ASP.NET

I spent a lot of time trying to figure out how to make the async calls work in our ASP.NET Web App. Can't make async calls without changing the pages and handlers to async but if you change the pages and handlers to async they can't write the to the session data. Other attempts to make the cal synchronously all resulted in deadlocks. The following works from the request thread without causing deadlocks:

var token = Task.Run(async () => { return await duoClient.ExchangeAuthorizationCodeFor2faResult(context.Request["code"], username); }).Result;

Suggest adding it to the example and/or to the documentation.

Find out why DoHealthCheck() Fails

Detailed Description

I have been working with this new API for a few days. It is pretty straightforward, but DoHealthCheck() kept returning false for an integration that we are attempting to migrate. I knew that Duo was "available" as a service, but we could not figure out why it was failing, because there is no mechanism in the Client class to retrieve the result.

ExchangeAuthorizationCodeFor2faResult() will throw all sorts of DuoExceptions, but DoHealthCheck() only looks at the "OK" status, and ignores any error message. Even an HttpRequestException exception (which could mean the API URL is incorrect) is hidden by this function.

Use Case

In my case, we were able to find out that the error was "invalid_client: Integration type does not support frameless access". The solution is easier once we had the error.

I don't know what the best solution is here. The way these two functions are designed is completely different, so I assume it was intentional that DoHealthCheck() not throw an exception. It could be that there are some serious problems like this that should still throw an exception, or the exception can be saved by the Client and we could check it if DoHealthCheck() returns false.

Maybe a different version of DoHealthCheck() that returns the HealthCheckResult object would be the easiest to implement without breaking anything. Those of us that want details can use the new function.

Workarounds

For our testing, we built the DLL from source and included an exception if the HealthCheckResult was not OK.

Duouniversal popup automatic login

issue :
DuoUniversal popup directly redirect to our home page without entering otp or without access push notification.

may i know the reason ?
Or do i need to do any settings in duo admin url to get the duo home page to select options?

Client does not follow Microsoft's guideance for HttpClient class

There are some unfortunate limitations that can occur with HttpClient when constructed in the default fashion. That class's defaults are not ideal for either usage model (HttpClient per request, or static/singleton) by default, especially for scenarios with meaningful load. Microsoft has substantial guidance about the use of this class because of these limitations.

This writeup briefly describes the two main issues talked about in Microsoft's guidance, and how they make may impact programs trying to use the Client class either usage model (instance per login request, or static/singleton instance). It does assume the reader has read Microsoft's guidance above.

From Duo's perspective this means potential problems for consumers. There are different problems for the one Client per login request model, and the single Static/Singleton instance.

Potential Issues with Client per Request

There is a limit to how many times you can close and reopen open a TCP connection to a given server in a any given time period (due to TCP's TIME_WAIT state, which lasts 4 minutes by default on Windows, and 60 seconds on Linux). So somebody uses an HttpClient per request under meaningful volume (like a dozen requests per second or so) they may hit problems. (Probably not a huge issue for most Duo Users, as a dozen logins per second continuously would be unlikely, but a brief period of many logins, like at the start of a work day could possibly be problematic).

However, the above is assuming you dispose of the HttpClient when you are done. Unfortunately, Client does not Implement IDisposable, so there is no way to dispose the HttpClient. If you use one per request while failing to dispose, well that also means that you are relying on the garbage collector to garbage collect the connection pool. (By default each HttpClient has its own connection pool, and connections are only closed after being idle for one minute). If you get a bunch of Signins without garbage collection running (or if these objects manage to get promoted to higher garbage collection tiers, which are collected less frequently) this could cause problems.

Microsoft's recommended way to fix this is using IHttpClientFactory, or (for .NET Core/5+ only) using a single static HttpClient instance with PooledConnectionLifetime set to some reasonable value to avoid the second problem.

Potential Issues when using a static/singleton instance of Client

On the other hand, having just a static/singleton HttpClient instance (which is what a static/singleton Client would do) can create issues with DNS changes assuming a enough traffic occurs that some pooled connections never reach their Idle timeout (2 minutes in .NET 5 or older, 1 minute in .NET 6+). While that is still a fair bit of login traffic, it is plausible for heavily used security sensitive apps (i.e. having short login session limits) at a larger enterprise.

There is no way to fix this for .NET Framework users while retaining a single HttpClient instance, so Microsoft's guidance for .NET Framework is to always use IHttpClientFactory. (You can mostly get away with singleton instances if DNS changes are not really a concern though).

Microsoft.IdentityModel.JsonWebTokens Issue

It seems DuoUniversal has an issue with Microsoft.IdentityModel.JsonWebTokens 6.31.0

I upgraded Microsoft.Graph in my project to 5.13.0 DuoUniversal began to fail with this error message.

ArgumentOutOfRangeException: IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS512', the key size must be greater than: '512' bits, key has '320' bits. (Parameter 'keyBytes')

It seems to fail when issuing the DuoHealthCheck

Health check fails from Linux machine, not from Windows machine

Hi,

We’re implementing 2fa authentication with DUO for an application of ours we’re building. This works perfectly when we run the software locally (redict url via ngrok) or on a Windows server, but not on our production server: the health check (.DoHealthCheck()) always fails.

We use the C# client from github: GitHub - duosecurity/duo_universal_csharp: Duo two-factor authentication for .NET web applications

To extract the problem from our application itself we created a small console app app which does nothing else than create a Duo Universal client with our settings, and run the health check and outputs if it succeeded or not. This also only works on the Windows machines (outputs ‘True’) and not the Linux machines (outputs ‘False’).

I suspect it has something to do differences between the SSL version and/or protocol in both the linux and windows machines.

I changed the DoHealthCheck a bit to output the error, and that says:
System.Security.Authentication.AuthenticationException: The remote certificate was rejected by the provided RemoteCertificateValidationCallback

Anyone any ideas how to fix this?

Some way to restrict or suggest phone numbers for a user?

We have employee phones, which we control the phone numbers for, of course. As a security measure, we want to add MFA, with the primary method being registration via these phones. We want to allow registration using only the phone numbers we provide, read from our database, which our web application can look up.
In the login flow, there's a simple login form like in the example, it then goes to Duo, but I see no way of passing anything but the username (company email in this case) to suggest the login/registration methods.
Any suggestions how we should handle this?

ClientBuilder should not require redirectUri

I need to use a different RedirectUri in my app for different pages/features, and I am forced to define it when I call new ClientBuilder(...).

Due to this, I cannot create an interface that I can inject and mock as shown in the example with IDuoClientProvider.

Is there a way we can make the RedirectUri a parameter on GenerateAuthUri()? Would that not make more sense in general?

Detailed Description

See above.

Use Case

I have various pages in my ASPNET Core Razor Pages app that require the user to verify their identity via a Duo push, each feature/vertical slice of my app should have their own callback page.

Workarounds

None yet, working on it now. Maybe a central callback page that takes its own redirectUri... or a keyed service, each page will have a key that resolves the client that is built to target a specific redirectUrl.

HttpClientHandler.ServerCertificateCustomValidationCallback requires .NET Framework 4.7.1

The repo states the library requires .NET 4.6.1, but this property did not exist until .NET Framework 4.7.1:

ServerCertificateCustomValidationCallback = certPinner

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.servercertificatecustomvalidationcallback?view=netframework-4.7.1

Trying to use the client on a Windows machine that does not have at least .NET Framework 4.7.1 would most likely cause a runtime error. It would be really nice to use this with my .NET 4.6.2 web app, but I foresee this being a reason I can't.

IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS512'

With Microsoft.IdentityModel.JsonWebTokens library version 6.32.0 and 6.31.0 , DuoUniversal produces following error:

IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS512', the key size must be greater than: '512' bits, key has '320' bits.
Parameter name: keyBytes

<StackTrace> <Frame>at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.ValidateKeySize(Byte[] keyBytes, String algorithm, Int32 expectedNumberOfBytes)</Frame> <Frame>at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateKeyedHashAlgorithm(Byte[] keyBytes, String algorithm)/Frame> <Frame>at Microsoft.IdentityModel.Tokens.DisposableObjectPool1.Allocate()</Frame> <Frame>at Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider.Sign(Byte[] input)</Frame> <Frame>at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(String input, SigningCredentials signingCredentials)</Frame> <Frame>at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateTokenPrivate(String payload, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials, String compressionAlgorithm, IDictionary2 additionalHeaderClaims, IDictionary2 additionalInnerHeaderClaims, String tokenType)</Frame> <Frame>at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(String payload, SigningCredentials signingCredentials)</Frame> <Frame>at DuoUniversal.JwtUtils.CreateSignedJwt(String clientId, String clientSecret, String audience, IDictionary2 additionalClaims)</Frame> <Frame>at DuoUniversal.Client.&lt;DoHealthCheck&gt;d__35.MoveNext()</Frame> </StackTrace>

While with version 6.29.0 it works fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.