vbjay / IDS4-Sample

Full demo of setting up an IDS4 instance and protecting an api and client app with it.
MIT License
6 stars 1 forks source link

Refresh Token is Null #126

Open Hamidnch opened 3 years ago

Hamidnch commented 3 years ago

Hi. Thanks for your solution. I'm used Skoruba Identityserver4 like your project. but when calling webapi from the mvc client, I deal with two problems. This is an action method in HomeController of the mvc client :

` [Authorize] public async Task WeatherAsync() { var token = await HttpContext.GetTokenAsync("access_token"); //ServicePointManager.ServerCertificateValidationCallback += // (sender, certificate, chain, sslPolicyErrors) => true;

        if (token == null) return null;

        var client = new RestClient(_configuration["WebApiBaseAddress"])
        {
            Authenticator = new JwtAuthenticator(token),
            //ServicePointManager.ServerCertificateValidationCallback
            //RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
            //{
            //    //return true;
            //    return sslPolicyErrors == SslPolicyErrors.None;
            //}
        };            
        var req = new RestRequest("/WeatherForecast", Method.GET);
        var res = await client.ExecuteAsync<List<WeatherForecast>>(req);
        return View(res.Data);
    }`

1- When debugging this code after calling that,i deal with this errors: **- InnerException = {"Cannot determine the frame size or a corrupted frame was received."}

I did a lot of searches in google and I know that the problem is related to Windows 10. But looking for a temporary solution to run in the local

2- I add some code for create refresh token: ` public class WebApiHttpClient : IWebApiHttpClient { private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger;

    public WebApiHttpClient(HttpClient httpClient, IConfiguration configuration,
        IHttpContextAccessor httpContextAccessor, ILogger<WebApiHttpClient> logger)
    {
        _httpClient = httpClient;
        _configuration = configuration;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async Task<HttpClient> GetHttpClientAsync()
    {
        string accessToken;
        var currentContext = _httpContextAccessor.HttpContext;
        var expiresAt = await currentContext.GetTokenAsync("expires_at");
        if (string.IsNullOrWhiteSpace(expiresAt)
            || ((DateTime.Parse(expiresAt).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow))
        {
            accessToken = await RenewTokens(currentContext);
        }
        else
        {
            accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
        }

        if (!string.IsNullOrWhiteSpace(accessToken))
        {
            _logger.LogInformation($"Using Access Token: {accessToken}");
            _httpClient.SetBearerToken(accessToken);
        }

        _httpClient.BaseAddress = new Uri(_configuration["WebApiBaseAddress"]);
        _httpClient.DefaultRequestHeaders.Accept.Clear();
        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        return _httpClient;
    }

    private async Task<string> RenewTokens(HttpContext currentContext)
    {
        // get the current HttpContext to access the tokens
        //var currentContext = _httpContextAccessor.HttpContext;

        var idpAddress = _configuration["IDPBaseAddress"];

        var disco = await _httpClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
        {
            Address = idpAddress
        });

        if (disco.IsError) throw new Exception(disco.Error);

        // get the saved refresh token
        var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

        // refresh the tokens
        var response = await _httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest
        {
            Address = disco.TokenEndpoint,

            ClientId = _configuration["ClientId"],
            ClientSecret = _configuration["ClientSecret"],

            RefreshToken = currentRefreshToken
        });

        if (response.IsError)
        {
            throw new Exception("Problem encountered while refreshing tokens.", response.Exception);
        }

        // update the tokens & expiration value
        var updatedTokens = new List<AuthenticationToken>
        {
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.IdToken,
                Value = response.IdentityToken
            },
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.AccessToken,
                Value = response.AccessToken
            },
            new AuthenticationToken
            {
                Name = OpenIdConnectParameterNames.RefreshToken,
                Value = response.RefreshToken
            }
        };

        var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
        updatedTokens.Add(new AuthenticationToken
        {
            Name = "expires_at",
            Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
        });

        // get authenticate result, containing the current principal & properties
        var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");

        // store the updated tokens
        currentAuthenticateResult.Properties.StoreTokens(updatedTokens);

        // sign in
        await currentContext.SignInAsync("Cookies",
            currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);

        // return the new access token
        return response.AccessToken;
    }    
}`

I also did this settings in mvc client startup: options.Scope.Add("offline_access"); and I did the same settings in clients: "AllowOfflineAccess": true, "AllowedScopes": [ "openid", "profile", "roles", "offline_access", "Webapi", "weather.write", "weather.read", "email", "address" ], Using of this class in the mvc client => HomeController :

` [HttpGet] public async Task LoadAll() { var httpClient = await _webApiHttpClient.GetHttpClientAsync(); var response = await httpClient.GetAsync("WeatherForecast");

        if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
            response.StatusCode == System.Net.HttpStatusCode.Forbidden)
        {
            return RedirectToAction("AccessDenied", "Authorization");
        }
        response.EnsureSuccessStatusCode();

        var responseContent = await response.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(responseContent);
        return View(result);
    }`

After executing this line, RefreshToken is equal null: var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

3- Another ambiguity that is there for me is that How should I open the login and register directly by clicking on the corresponding link? Is correct the use of the Absolute address? like this: Register

Thanks for your attention.

vbjay commented 3 years ago

Did you follow ssl instructions in readme?

vbjay commented 3 years ago

Also https://github.com/vbjay/IDS4-Sample#room-for-improvement

Hamidnch commented 3 years ago

Thanks, The problem number one was solved

Hamidnch commented 3 years ago

But problem number two (refresh token) Not resolved yet. I deal with Invalid_grant error.

vbjay commented 3 years ago

The oidcclient that was setup is using authorization code not client secret.

vbjay commented 3 years ago

I wouldn't link absolutely. I'd just link to an endpoint that is locked down by authorize and let it redirect to the right place.

Hamidnch commented 3 years ago

I wouldn't link absolutely. I'd just link to an endpoint that is locked down by authorize and let it redirect to the right place.

I have an Web App .net core and I want to redirect the user to the registration page after clicking on the register link in my home page. What's the right thing to do? Thanks.

Hamidnch commented 3 years ago

The oidcclient that was setup is using authorization code not client secret.

*I did not catch What settings are better generally for an asp.net core client to connect with Identity server4.

Thanks for all replies.*

vbjay commented 3 years ago

Like I said. Go here https:/demoids.vbjaysolutions.com/sts. Make sure you are logged out. Now go to https:/demoids.vbjaysolutions.com/mvc/privacy. Watch What happens.

See how the site links to an on site link and automatically goes to login page?

vbjay commented 3 years ago

Fixed url. Had a typo.

Hamidnch commented 3 years ago

https:/demoids.vbjaysolutions.com/mvc/privacy

I fully understand the redirect to the login page. Now how can I move the user to the registration page. Is this correct: Using of this address https://localhost:44310/account/register for anchor href

vbjay commented 3 years ago

Again. You are failing to get my point. Your app should not do a hard link. Set an endpoint in your app not ids4 url that is locked down to require authorize. That way it acts just like the privacy link in my demo.

On Thu, Oct 28, 2021, 12:57 PM Hamidnch @.***> wrote:

https:/demoids.vbjaysolutions.com/mvc/privacy

I fully understand the redirect to the login page. Now how can I move the user to the registration page. Is this correct: Using of this address https://localhost:44310/account/register for anchor href

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/vbjay/IDS4-Sample/issues/126#issuecomment-954029427, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA3WDMKWVSESKA7JO66BPMDUJF6IDANCNFSM5G4AGCWA .

vbjay commented 3 years ago

You want the user to login or register not directly register. If you turn registration on you get a register button. They can chose to register or login.

Hamidnch commented 3 years ago

I'm sorry to bother you. Please say this too, how to Set an endpoint in my app. Execuse me.

vbjay commented 3 years ago

Create an action on a controller that has an authorize attribute in your app. Point tge link to that in your view. See how I did privacy action in the home controller and the privacy link in layout. https://github.com/vbjay/IDS4-Sample/blob/e9aa761a334dd5231167194df803607f791f655c/src/mvc/Views/Shared/_Layout.cshtml#L30. See how it's local to the app? https://github.com/vbjay/IDS4-Sample/blob/e9aa761a334dd5231167194df803607f791f655c/src/mvc/Controllers/HomeController.cs#L39.

That way you don't hardcode to a url that could change. https://betterprogramming.pub/demeters-law-don-t-talk-to-strangers-87bb4af11694

So you need to act like sts and admin ui are different applications and are controlled by 3rd party. They may change where register points to. So instead you let the oidc client middleware handle where it should go.

vbjay commented 3 years ago

Not only that...they then get redirected to your app after logging in or registering.

Hamidnch commented 3 years ago

Thanks for your explanation. If possible, put a login and registration link on the first page of your mvc client example and then move the users to the login or register pages correctly. This will be very useful for other people like me.

vbjay commented 3 years ago

Privacy already provides the same functionality you would replicate. You would just point the link to whatever local endpoint you want in your application.

vbjay commented 3 years ago

Also if you go to https://demoids.vbjaysolutions.com. It takes you to the sts app by default which is where you would log in/register. The mvc app https://demoids.vbjaysolutions.com/mvc has a home page that is not locked down and then other links that are. I already demonstrate the concept quite well. The privacy and the weather link would be samples of that functionality. It doesn't matter if the link says login/register or anything else. It's the same concept.

parlive commented 3 years ago

Privacy already provides the same functionality you would replicate. You would just point the link to whatever local endpoint you want in your application.

hi According to your Answer, I must have an action like privacy And inside it, I do the following

public IActionResult Privacy() { return Redirect("https://localhost:44310/account/register"); }

Definitely do not need the authorize attribute to register, right?

vbjay commented 3 years ago

Nope. Did you see me do a redirect with privacy link?

On Sat, Oct 30, 2021, 7:22 AM parlive @.***> wrote:

Privacy already provides the same functionality you would replicate. You would just point the link to whatever local endpoint you want in your application.

hi According to your Answer, I must have an action like privacy And inside it, I do the following

public IActionResult Privacy() { return Redirect("https://localhost:44310/account/register"); }

Definitely do not need the authorize attribute to register, right?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/vbjay/IDS4-Sample/issues/126#issuecomment-955193555, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA3WDMIOCOVZTYKAAINC23LUJPIOFANCNFSM5G4AGCWA .

vbjay commented 3 years ago
 ​ <​a​ ​class​=​"​nav-link text-dark​"​ ​asp-area​=​"​"​ ​asp-controller​=​"​Home​"​ ​asp-action​=​"​Privacy​"​>Privacy</​a​>

links to the privacy action in the home controller.

Home controller does this

 ​        [​Authorize​] 
 ​        ​public​ ​IActionResult​ ​Privacy​() 
 ​        { 
 ​            ​return​ ​View​(); 
 ​        } 
vbjay commented 3 years ago

https://github.com/vbjay/IDS4-Sample/blob/master/src/mvc/Views/Home/Privacy.cshtml is the view that shows the ui in the privacy page.

vbjay commented 3 years ago

https://github.com/vbjay/IDS4-Sample/blob/master/src/mvc/Startup.cs look at how it is configured.

vbjay commented 3 years ago

The whole point is you now do not need a login/register link or if you must have one, just put authorize on the action and it will work.

vbjay commented 3 years ago

By the way, yes... I know I should grab the details in a model and send to the view. This was a quick view to show what the browser knows. So you could then take the tokens to jwt.io and examine and see the claims the browser knows.

parlive commented 3 years ago

By the way, yes... I know I should grab the details in a model and send to the view. This was a quick view to show what the browser knows. So you could then take the tokens to jwt.io and examine and see the claims the browser knows.

Apparently you did not understand my request I ask my questions again I want to have a very simple and explicit button to login and register in the login form If the user needs to register or login, click on each to be transferred to the desired page without the need for redirecting and authentication I think it is enough to refer to its address The reason for asking my question is to make sure of this Thank you very much for your time

vbjay commented 3 years ago

If you are using ids4 for your user management and authentication then you need to get the idea. Does it matter if the link says privacy or login or whatever...it's the same thing. I am telling you you need to do exactly what I did with privacy link. Let ids4 do its job.

On Sun, Oct 31, 2021, 1:05 AM parlive @.***> wrote:

By the way, yes... I know I should grab the details in a model and send to the view. This was a quick view to show what the browser knows. So you could then take the tokens to jwt.io and examine and see the claims the browser knows.

Apparently you did not understand my request I ask my questions again I want to have a very simple and explicit button to login and register in the login form If the user needs to register or login, click on each to be transferred to the desired page without the need for redirecting and authentication I think it is enough to refer to its address The reason for asking my question is to make sure of this Thank you very much for your time

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/vbjay/IDS4-Sample/issues/126#issuecomment-955637894, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA3WDMMIVZGL54S7XW6LCULUJTFABANCNFSM5G4AGCWA .

vbjay commented 3 years ago

It's the authorize attribute that causes it to redirect if not authenticated to the page in ids4 that let's them login OR register.

vbjay commented 3 years ago

I have register turned off in my live site but allow external logins which create accounts. If you flip the setting in app settings a register button will show or not.