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

Using AuthorizationCodeWebApp class in .NET8 WebApi #2856

Closed
akordowski opened this issue Oct 8, 2024 · 9 comments
Closed

Using AuthorizationCodeWebApp class in .NET8 WebApi #2856

akordowski opened this issue Oct 8, 2024 · 9 comments
Assignees
Labels
needs more info This issue needs more information from the customer to proceed. priority: p3 Desirable enhancement or fix. May not be included in next release. type: question Request for information or clarification. Not an issue.

Comments

@akordowski
Copy link

I would like to pick up on the issue #2826. I am trying for days to figure out how to use the AuthorizationCodeWebApp class in a .NET 8 WebApi. I couldn't find any example so far, so I really hope any one can help on this. Here the code I have so far:

[ApiController]
[Route("[controller]")]
public class YouTubeController : ControllerBase
{
    private readonly GoogleAuthorizationCodeFlow _flow;

    public YouTubeController()
    {
        var clientSecrets = new ClientSecrets
        {
            ClientId = "...",
            ClientSecret = "..."
        };

        _flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = clientSecrets,
            Scopes = new[] { YouTubeService.Scope.YoutubeReadonly },
            DataStore = new FileDataStore("YouTubeApi")
        });
    }

    [HttpGet(Name = "YouTube")]
    public async Task<IActionResult> Get(CancellationToken cancellationToken)
    {
        var authResult = await new AuthorizationCodeWebApp(
                _flow,
                "https://localhost:7187/YouTube/Redirect",
                "https://localhost:7187/YouTube/State")
            .AuthorizeAsync("user", cancellationToken);

        if (authResult.RedirectUri != null)
        {
            return Redirect(authResult.RedirectUri);
        }

        return Ok();
    }

    [HttpGet("Redirect")]
    public async Task<IActionResult> Redirect(
        [FromQuery] string code,
        [FromQuery] string state,
        [FromQuery] string scope,
        CancellationToken cancellationToken)
    {
        var tokenResponse = await _flow.ExchangeCodeForTokenAsync("user", code, "https://localhost:7187/YouTube/State", cancellationToken);

        return Ok();
    }

    [HttpPost("State")]
    public async Task<IActionResult> State(CancellationToken cancellationToken)
    {
        return Ok();
    }
}

The Redirect() method is called with https://localhost:7187/YouTube/Redirect?state=https://localhost:7187/YouTube/State00801791&code=...&scope=https://www.googleapis.com/auth/youtube.readonly. The state https://localhost:7187/YouTube/State00801791 is also stored in the DataStore. But I have no idea how to use it. I tried to call _flow.ExchangeCodeForTokenAsync() but it throws an exception with the message TokenResponseException: Error:"redirect_uri_mismatch", Description:"Bad Request", Uri:"". Is that event the way to go?

The API has a own authentication provider. The authorization on Google should be rather a delegate authorization just for the services to use, not the API itself.

Can anyone provide an example how to proceed or give an hint at least? Any help is much appreciated! Thanks in advance.

@akordowski akordowski added priority: p3 Desirable enhancement or fix. May not be included in next release. type: question Request for information or clarification. Not an issue. labels Oct 8, 2024
@jskeet jskeet assigned amanda-tarafa and unassigned jskeet Oct 8, 2024
@jskeet
Copy link
Collaborator

jskeet commented Oct 8, 2024

@amanda-tarafa should be able to help you tomorrow.

@amanda-tarafa
Copy link
Contributor

The redirect URI you pass to ExchangeCodeForTokenAsync needs to be allowlisted on your credentials page (where you obtained your client ID and secret). Usually, you can use the same you used as callback for obtaining the authorization code, in your case https://localhost:7187/YouTube/Redirect.

Once you have obtained a TokenResponse, you should redirect the user to the original URI they requested, where it was found they were not authenticated. In your case, that'd be the URI for the Get controller method. You can use state to pass this URI all the way down to authorization code exchange.

State is not a URI in your application that Google will redirect the user to. State is whatever you need it to be so you can propagate information during the authenticaiton process. As I said earlier, this may be used to pass the original URI the user requested and/or the user identifier, etc.

@akordowski
Copy link
Author

@amanda-tarafa Thank you for your response. I will try it on weekend and give you a feedback.

@amanda-tarafa amanda-tarafa added the needs more info This issue needs more information from the customer to proceed. label Oct 10, 2024
@akordowski
Copy link
Author

@amanda-tarafa I tested your suggestions and here are my findings.

The call of the ExchangeCodeForTokenAsync() method works only if I pass the same redirectUri as used in the AuthorizationCodeWebApp class (in my example https://localhost:7187/YouTube/Redirect), although both urls are allowlisted in the console. My guess is that the redirectUri is somehow encoded in the received code. Am I right?

I also noticed that the state value is altered. In my example I recieve in the state parameter of the Redirect() method the following value https://localhost:7187/YouTube/State00801791. According to the source code a randon string is added to the end of the state.

if (Flow.DataStore != null)
{
var rndString = new string('9', StateRandomLength);
var random = new Random().Next(int.Parse(rndString)).ToString("D" + StateRandomLength);
oauthState += random;
await Flow.DataStore.StoreAsync(StateKey + userId, oauthState).ConfigureAwait(false);
}
codeRequest.State = oauthState;

Is that intentional? When yes, why? And how can I obtain the original data? As the string is random length I can not just strip the end of the string.

Thank you for your help.

@amanda-tarafa
Copy link
Contributor

The documentation is not specific about which URIs may be passed for code exchange beyond the fact that they need to be allowlisted for your client ID. But yes, it's usual to pass the same URI. I cannot say with certainty that's because the URI is encoded in the authorization code or that the Auth service can map the code with the URI, etc. nor that it will happen for all cases. Maybe the OAuth team can give you a better answer, you can find their support channels at the bottom of the documentation link above.

The random number added will always have AuthorizationCodeWebApp.StateRandomLength, that's what the "D" + StateRandomLength parameters is doing in

var random = new Random().Next(int.Parse(rndString)).ToString("D" + StateRandomLength);

This is a primitive means to allow you to match a code authorization request with it's specific callback, if you need to do so. There's a TODO in the code to allow deactivating the addition of this random number, we might look into that at some point, but I can't give an ETA at the moment. For the time being, you can confidently remove the last AuthorizationCodeWebApp.StateRandomLength characters from the state you received alongside the authorization code.

Let me know if you have more questions.

@amanda-tarafa
Copy link
Contributor

@akordowski I'll be closing this issue as I believe I've replied to all your questions and things are now working. Leave a new comment is you believe otherwise.

@akordowski
Copy link
Author

@amanda-tarafa Thank you for the explanation, that helped me a lot.

The random number added will always have AuthorizationCodeWebApp.StateRandomLength

I was confused by the name of the property and thought that the lenght of the state is random. As it is 8 chars in length, so I can just cut it from the end.

There's a TODO in the code to allow deactivating the addition of this random number

Any interest for a PR regarding this TODO?

Thank you for your help!

@amanda-tarafa
Copy link
Contributor

Any interest for a PR regarding this TODO?

If you are up for a potentially slow review and back and forth, thent that's fine. We are somewhat busy at the moment. Also, consider that we wouldn't accept a breaking change for this, nor "weirdness" around flow creation, etc. Maybe draft a PR in terms of how the change will affect the library public surface, and when we have that pinned down you can work on implementation and tests.

@akordowski
Copy link
Author

Ok, will see if I can spare some time ;) Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs more info This issue needs more information from the customer to proceed. priority: p3 Desirable enhancement or fix. May not be included in next release. type: question Request for information or clarification. Not an issue.
Projects
None yet
Development

No branches or pull requests

3 participants