/// <inheritdoc/> public async Task <TokenResponse> RefreshUserAccessTokenAsync( string refreshToken, UserAccessTokenParameters parameters = null, CancellationToken cancellationToken = default) { _logger.LogDebug("Refreshing refresh token: {token}", refreshToken); parameters ??= new UserAccessTokenParameters(); var requestDetails = await _configService.GetRefreshTokenRequestAsync(parameters); requestDetails.RefreshToken = refreshToken; #if NET5_0 requestDetails.Options.TryAdd(AccessTokenManagementDefaults.AccessTokenParametersOptionsName, parameters); #elif NETCOREAPP3_1 requestDetails.Properties[AccessTokenManagementDefaults.AccessTokenParametersOptionsName] = parameters; #endif if (!string.IsNullOrEmpty(parameters.Resource)) { requestDetails.Resource.Add(parameters.Resource); } var httpClient = _httpClientFactory.CreateClient(AccessTokenManagementDefaults.BackChannelHttpClientName); return(await httpClient.RequestRefreshTokenAsync(requestDetails, cancellationToken)); }
/// <inheritdoc/> public async Task <UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null) { parameters ??= new UserAccessTokenParameters(); var result = await _contextAccessor.HttpContext.AuthenticateAsync(parameters.SignInScheme); if (!result.Succeeded) { return(null); } if (result.Properties == null) { return(null); } var tokens = result.Properties.Items.Where(i => i.Key.StartsWith(TokenPrefix)).ToList(); if (tokens == null || !tokens.Any()) { throw new InvalidOperationException( "No tokens found in cookie properties. SaveTokens must be enabled for automatic token refresh."); } var tokenName = $"{TokenPrefix}{OpenIdConnectParameterNames.AccessToken}"; if (!string.IsNullOrEmpty(parameters.Resource)) { tokenName += $"::{parameters.Resource}"; } var expiresName = $"{TokenPrefix}expires_at"; if (!string.IsNullOrEmpty(parameters.Resource)) { expiresName += $"::{parameters.Resource}"; } var accessToken = tokens.SingleOrDefault(t => t.Key == tokenName); var refreshToken = tokens.SingleOrDefault(t => t.Key == $"{TokenPrefix}{OpenIdConnectParameterNames.RefreshToken}"); var expiresAt = tokens.SingleOrDefault(t => t.Key == expiresName); DateTimeOffset?dtExpires = null; if (expiresAt.Value != null) { dtExpires = DateTimeOffset.Parse(expiresAt.Value, CultureInfo.InvariantCulture); } return(new UserAccessToken { AccessToken = accessToken.Value, RefreshToken = refreshToken.Value, Expiration = dtExpires }); }
/// <summary> /// Set an access token on the HTTP request /// </summary> /// <param name="request"></param> /// <param name="forceRenewal"></param> /// <returns></returns> protected virtual async Task SetTokenAsync(HttpRequestMessage request, bool forceRenewal) { var parameters = new UserAccessTokenParameters { SignInScheme = _parameters.SignInScheme, ChallengeScheme = _parameters.ChallengeScheme, Resource = _parameters.Resource, ForceRenewal = forceRenewal, Context = _parameters.Context }; var token = await _httpContextAccessor.HttpContext.GetUserAccessTokenAsync(parameters); if (!string.IsNullOrWhiteSpace(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } }
/// <inheritdoc/> public async Task RevokeRefreshTokenAsync( ClaimsPrincipal user, UserAccessTokenParameters parameters = null, CancellationToken cancellationToken = default) { parameters ??= new UserAccessTokenParameters(); var userToken = await _userTokenStore.GetTokenAsync(user); if (!string.IsNullOrEmpty(userToken?.RefreshToken)) { var response = await _tokenEndpointService.RevokeRefreshTokenAsync(userToken.RefreshToken, parameters, cancellationToken); if (response.IsError) { _logger.LogError("Error revoking refresh token. Error = {error}", response.Error); } } }
/// <inheritdoc /> public virtual async Task <RefreshTokenRequest> GetRefreshTokenRequestAsync(UserAccessTokenParameters parameters = null) { var(options, configuration) = await GetOpenIdConnectSettingsAsync(parameters?.ChallengeScheme ?? _userAccessTokenManagementOptions.SchemeName); var requestDetails = new RefreshTokenRequest { Address = configuration.TokenEndpoint, ClientId = options.ClientId, ClientSecret = options.ClientSecret }; var assertion = await CreateAssertionAsync(); if (assertion != null) { requestDetails.ClientAssertion = assertion; } return(requestDetails); }
/// <inheritdoc/> public async Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null) { parameters ??= new UserAccessTokenParameters(); var result = await _contextAccessor.HttpContext.AuthenticateAsync(parameters.SignInScheme); if (!result.Succeeded) { throw new Exception("Can't store tokens. User is anonymous"); } // in case you want to filter certain claims before re-issuing the authentication session var transformedPrincipal = await FilterPrincipalAsync(result.Principal); var tokenName = OpenIdConnectParameterNames.AccessToken; if (!string.IsNullOrEmpty(parameters.Resource)) { tokenName += $"::{parameters.Resource}"; } var expiresName = "expires_at"; if (!string.IsNullOrEmpty(parameters.Resource)) { expiresName += $"::{parameters.Resource}"; } result.Properties.Items[$".Token.{tokenName}"] = accessToken; result.Properties.Items[$".Token.{expiresName}"] = expiration.ToString("o", CultureInfo.InvariantCulture); if (refreshToken != null) { result.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, refreshToken); } await _contextAccessor.HttpContext.SignInAsync(parameters.SignInScheme, transformedPrincipal, result.Properties); }
/// <inheritdoc/> public async Task <TokenRevocationResponse> RevokeRefreshTokenAsync( string refreshToken, UserAccessTokenParameters parameters = null, CancellationToken cancellationToken = default) { _logger.LogDebug("Revoking refresh token: {token}", refreshToken); parameters ??= new UserAccessTokenParameters(); var requestDetails = await _configService.GetTokenRevocationRequestAsync(parameters); requestDetails.Token = refreshToken; requestDetails.TokenTypeHint = OidcConstants.TokenTypes.RefreshToken; #if NET5_0_OR_GREATER requestDetails.Options.TryAdd(AccessTokenManagementDefaults.AccessTokenParametersOptionsName, parameters); #else requestDetails.Properties[AccessTokenManagementDefaults.AccessTokenParametersOptionsName] = parameters; #endif var httpClient = _httpClientFactory.CreateClient(AccessTokenManagementDefaults.BackChannelHttpClientName); return(await httpClient.RevokeTokenAsync(requestDetails, cancellationToken)); }
/// <inheritdoc/> public async Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null) { parameters ??= new UserAccessTokenParameters(); var result = await _contextAccessor.HttpContext.AuthenticateAsync(parameters.SignInScheme); if (!result.Succeeded) { throw new Exception("Can't store tokens. User is anonymous"); } var tokenName = OpenIdConnectParameterNames.AccessToken; if (!string.IsNullOrEmpty(parameters.Resource)) { tokenName += $"::{parameters.Resource}"; } var expiresName = "expires_at"; if (!string.IsNullOrEmpty(parameters.Resource)) { expiresName += $"::{parameters.Resource}"; } result.Properties.Items[$".Token.{tokenName}"] = accessToken; result.Properties.Items[$".Token.{expiresName}"] = expiration.ToString("o", CultureInfo.InvariantCulture); if (refreshToken != null) { result.Properties.UpdateTokenValue(OpenIdConnectParameterNames.RefreshToken, refreshToken); } // can not work in blazor server project. better way to signin? // ref https://github.com/IdentityModel/IdentityModel.AspNetCore/issues/186 // await _contextAccessor.HttpContext.SignInAsync(parameters.SignInScheme, result.Principal, result.Properties); }
/// <summary> /// ctor /// </summary> /// <param name="httpContextAccessor"></param> /// <param name="parameters"></param> public UserAccessTokenHandler(IHttpContextAccessor httpContextAccessor, UserAccessTokenParameters parameters = null) { _httpContextAccessor = httpContextAccessor; _parameters = parameters ?? new UserAccessTokenParameters(); }
/// <inheritdoc/> public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null) { // todo return(Task.CompletedTask); }
/// <inheritdoc/> public async Task <string> GetUserAccessTokenAsync( ClaimsPrincipal user, UserAccessTokenParameters parameters = null, CancellationToken cancellationToken = default) { parameters ??= new UserAccessTokenParameters(); if (user == null || !user.Identity.IsAuthenticated) { return(null); } var userName = user.FindFirst(JwtClaimTypes.Name)?.Value ?? user.FindFirst(JwtClaimTypes.Subject)?.Value ?? "unknown"; var userToken = await _userTokenStore.GetTokenAsync(user, parameters); if (userToken == null) { _logger.LogDebug("No token data found in user token store."); return(null); } if (userToken.AccessToken.IsPresent() && userToken.RefreshToken.IsMissing()) { _logger.LogDebug("No refresh token found in user token store for user {user} / resource {resource}. Returning current access token.", userName, parameters.Resource ?? "default"); return(userToken.AccessToken); } if (userToken.AccessToken.IsMissing() && userToken.RefreshToken.IsPresent()) { _logger.LogDebug( "No access token found in user token store for user {user} / resource {resource}. Trying to refresh.", userName, parameters.Resource ?? "default"); } var dtRefresh = DateTimeOffset.MinValue; if (userToken.Expiration.HasValue) { dtRefresh = userToken.Expiration.Value.Subtract(_options.User.RefreshBeforeExpiration); } if (dtRefresh < _clock.UtcNow || parameters.ForceRenewal) { _logger.LogDebug("Token for user {user} needs refreshing.", userName); try { return(await UserRefreshDictionary.GetOrAdd(userToken.RefreshToken, _ => { return new Lazy <Task <string> >(async() => { var refreshed = await RefreshUserAccessTokenAsync(user, parameters, cancellationToken); return refreshed.AccessToken; }); }).Value); } finally { UserRefreshDictionary.TryRemove(userToken.RefreshToken, out _); } } return(userToken.AccessToken); }
private async Task <TokenResponse> RefreshUserAccessTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters, CancellationToken cancellationToken = default) { var userToken = await _userTokenStore.GetTokenAsync(user); var response = await _tokenEndpointService.RefreshUserAccessTokenAsync(userToken.RefreshToken, parameters, cancellationToken); if (!response.IsError) { var expiration = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn); await _userTokenStore.StoreTokenAsync(user, response.AccessToken, expiration, response.RefreshToken, parameters); } else { _logger.LogError("Error refreshing access token. Error = {error}", response.Error); } return(response); }
/// <inheritdoc /> public virtual async Task <TokenRevocationRequest> GetTokenRevocationRequestAsync(UserAccessTokenParameters parameters = null) { var(options, configuration) = await GetOpenIdConnectSettingsAsync(parameters?.ChallengeScheme ?? _userAccessTokenManagementOptions.SchemeName); var requestDetails = new TokenRevocationRequest { Address = configuration.AdditionalData[OidcConstants.Discovery.RevocationEndpoint].ToString(), ClientId = options.ClientId, ClientSecret = options.ClientSecret }; var assertion = await CreateAssertionAsync(); if (assertion != null) { requestDetails.ClientAssertion = assertion; } return(requestDetails); }