generated from kolosovpetro/github-latex-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add refreshtokenflow_en.txt | add authenticationflow_en.txt | add cod…
…e snippets
- Loading branch information
1 parent
1617ae1
commit d8dcc40
Showing
15 changed files
with
958 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
\begin{spverbatim} | ||
serviceCollection | ||
.AddAuthentication(options => {...}) | ||
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => {...}) | ||
.AddOpenIdConnect(AuthConstants.AppOidc, options => | ||
{ | ||
... | ||
options.Scope.Add("openid"); | ||
}); | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
\begin{spverbatim} | ||
public class UserSessionEntity | ||
{ | ||
public Guid Id { get; private set; } | ||
public DateTimeOffset CreatedAt { get; set; } | ||
public DateTimeOffset ExpiresAt { get; set; } | ||
public DateTimeOffset UpdatedAt { get; set; } | ||
public DateTimeOffset DateOfLastAccess { get; set; } | ||
public byte[] Value { get; set; } | ||
} | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
\begin{spverbatim} | ||
public class TicketStore : ITicketStore | ||
{ | ||
private readonly DatabaseContext _context; | ||
private readonly TicketSerializer _ticketSerializer; | ||
private readonly HttpClient _httpClient; | ||
private readonly AzureAdConfiguration _azureAdConfiguration; | ||
private readonly IMemoryCache _memoryCache; | ||
private readonly MemoryCacheEntryOptions _memoryCacheEntryOptions; | ||
|
||
public TicketStore( | ||
DatabaseContext context, | ||
TicketSerializer ticketSerializer, | ||
HttpClient httpClient, | ||
AzureAdConfiguration azureAdConfiguration, | ||
IMemoryCache memoryCache) | ||
{ | ||
_context = context; | ||
_ticketSerializer = ticketSerializer; | ||
_httpClient = httpClient; | ||
_azureAdConfiguration = azureAdConfiguration; | ||
_memoryCache = memoryCache; | ||
|
||
_memoryCacheEntryOptions = new MemoryCacheEntryOptions() | ||
.SetSlidingExpiration(TimeSpan.FromSeconds(15)) | ||
.SetAbsoluteExpiration(TimeSpan.FromSeconds(100)); | ||
} | ||
|
||
public async Task<string> StoreAsync(AuthenticationTicket ticket) | ||
{ | ||
var idToken = ticket.Properties.GetTokenValue(TokenTypes.IdentityToken); | ||
var accessToken = ticket.Properties.GetTokenValue(TokenTypes.AccessToken); | ||
var refreshToken = ticket.Properties.GetTokenValue(TokenTypes.RefreshToken); | ||
|
||
var handler = new JwtSecurityTokenHandler(); | ||
var decodeToken = handler.ReadToken(idToken) as JwtSecurityToken; | ||
var ticketExpiresUtc = ticket.Properties.ExpiresUtc; | ||
|
||
if (decodeToken == null) | ||
{ | ||
throw new StoreException("Read token error"); | ||
} | ||
|
||
if (ticketExpiresUtc.HasValue == false) | ||
{ | ||
throw new StoreException("Ticket ExpiresUtc value does not exist"); | ||
} | ||
|
||
var sessionId = decodeToken.Claims.First(x => x.Type == ClaimsConstants.Sid).Value; | ||
var userSession = await _context.UserSessions.FirstOrDefaultAsync(x => x.Id == new Guid(sessionId)); | ||
|
||
if (userSession != null) | ||
{ | ||
var deserializedTicket = _ticketSerializer.Deserialize(userSession.Value); | ||
|
||
if (deserializedTicket == null) | ||
{ | ||
throw new StoreException("Deserialization ticket error"); | ||
} | ||
|
||
if (accessToken == null || refreshToken == null || idToken == null) | ||
{ | ||
throw new StoreException("Access token, refresh token, identity token are not existing"); | ||
} | ||
|
||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.AccessToken, accessToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.RefreshToken, refreshToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.IdentityToken, idToken); | ||
|
||
var serializedTicket = _ticketSerializer.Serialize(deserializedTicket); | ||
|
||
userSession.UpdateValue(serializedTicket); | ||
userSession.UpdateExpiresAt(ticketExpiresUtc.Value); | ||
|
||
_context.UserSessions.Update(userSession); | ||
await _context.SaveChangesAsync(); | ||
|
||
_memoryCache.Set(sessionId, ticket, _memoryCacheEntryOptions); | ||
} | ||
|
||
if (userSession == null) | ||
{ | ||
var serializedTicket = _ticketSerializer.Serialize(ticket); | ||
|
||
var newUserSession = new UserSessionEntity(new Guid(sessionId), ticketExpiresUtc.Value, serializedTicket); | ||
|
||
_context.UserSessions.Add(newUserSession); | ||
await _context.SaveChangesAsync(); | ||
|
||
_memoryCache.Set(sessionId, ticket, _memoryCacheEntryOptions); | ||
} | ||
|
||
return sessionId; | ||
} | ||
|
||
public async Task RenewAsync(string key, AuthenticationTicket authenticationTicket) | ||
{ | ||
var userSession = await _context.UserSessions.FirstOrDefaultAsync(x => x.Id == new Guid(key)); | ||
|
||
if (userSession == null) | ||
{ | ||
throw new StoreException("UserSession does not exist"); | ||
} | ||
|
||
var deserializedTicket = _ticketSerializer.Deserialize(userSession.Value); | ||
|
||
if (deserializedTicket == null) | ||
{ | ||
throw new StoreException("Deserialization ticket error"); | ||
} | ||
|
||
var refreshToken = deserializedTicket.Properties.GetTokenValue(TokenTypes.RefreshToken); | ||
|
||
var response = await RequestRefreshTokenAsync(refreshToken); | ||
|
||
if (response.AccessToken == null || response.RefreshToken == null || response.IdentityToken == null) | ||
{ | ||
await RemoveAsync(key); | ||
return; | ||
} | ||
|
||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.AccessToken, response.AccessToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.RefreshToken, response.RefreshToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.IdentityToken, response.IdentityToken); | ||
|
||
var serializedTicket = _ticketSerializer.Serialize(deserializedTicket); | ||
|
||
userSession.UpdateValue(serializedTicket); | ||
userSession.UpdateExpiresAt(userSession.ExpiresAt.AddSeconds(response.ExpiresIn)); | ||
|
||
_context.UserSessions.Update(userSession); | ||
await _context.SaveChangesAsync(); | ||
} | ||
|
||
public async Task<AuthenticationTicket> RetrieveAsync(string key) | ||
{ | ||
var isMemoryCacheTicketExist = _memoryCache.TryGetValue(key, out AuthenticationTicket memoryCacheTicket); | ||
|
||
if (isMemoryCacheTicketExist) | ||
{ | ||
return memoryCacheTicket; | ||
} | ||
|
||
var userSession = await _context.UserSessions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == new Guid(key)); | ||
|
||
if (userSession == null) | ||
{ | ||
return null; | ||
} | ||
|
||
var deserializedTicket = _ticketSerializer.Deserialize(userSession.Value); | ||
|
||
if (deserializedTicket == null) | ||
{ | ||
throw new StoreException("Deserialization ticket error"); | ||
} | ||
|
||
if (DateTimeOffset.UtcNow > userSession.ExpiresAt) | ||
{ | ||
var refreshToken = deserializedTicket.Properties.GetTokenValue(TokenTypes.RefreshToken); | ||
|
||
if (refreshToken == null) | ||
{ | ||
throw new StoreException("Refresh token does not exist"); | ||
} | ||
|
||
var response = await RequestRefreshTokenAsync(refreshToken); | ||
|
||
if (response.AccessToken == null || response.RefreshToken == null || response.IdentityToken == null) | ||
{ | ||
await RemoveAsync(key); | ||
return null; | ||
} | ||
|
||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.AccessToken, response.AccessToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.RefreshToken, response.RefreshToken); | ||
deserializedTicket.Properties.UpdateTokenValue(TokenTypes.IdentityToken, response.IdentityToken); | ||
|
||
var serializedTicket = _ticketSerializer.Serialize(deserializedTicket); | ||
|
||
userSession.UpdateValue(serializedTicket); | ||
userSession.UpdateExpiresAt(DateTimeOffset.UtcNow.AddSeconds(response.ExpiresIn)); | ||
} | ||
|
||
userSession.UpdateDateOfLastAccess(); | ||
_context.UserSessions.Update(userSession); | ||
|
||
await _context.SaveChangesAsync(); | ||
|
||
_memoryCache.Set(key, deserializedTicket, _memoryCacheEntryOptions); | ||
|
||
return deserializedTicket; | ||
} | ||
|
||
public async Task RemoveAsync(string key) | ||
{ | ||
var userSession = await _context.UserSessions.FirstAsync(x => x.Id == new Guid(key)); | ||
|
||
if (userSession == null) return; | ||
|
||
_context.UserSessions.Remove(userSession); | ||
await _context.SaveChangesAsync(); | ||
} | ||
|
||
private async Task<TokenResponse> RequestRefreshTokenAsync(string refreshToken) | ||
{ | ||
var refreshTokenRequest = new RefreshTokenRequest | ||
{ | ||
Address = _azureAdConfiguration.AzureAdTokenUrl, | ||
ClientId = _azureAdConfiguration.ClientId.ToString(), | ||
ClientSecret = _azureAdConfiguration.ClientSecret, | ||
GrantType = GrantType.RefreshToken, | ||
Scope = _azureAdConfiguration.Scopes, | ||
RefreshToken = refreshToken | ||
}; | ||
|
||
var response = await _httpClient.RequestRefreshTokenAsync(refreshTokenRequest); | ||
|
||
return response; | ||
} | ||
} | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
\begin{spverbatim} | ||
public static class TicketStoreDependencyInjection | ||
{ | ||
public static IServiceCollection AddTicketStore(this IServiceCollection serviceCollection) | ||
{ | ||
var serviceProvider = serviceCollection.BuildServiceProvider(); | ||
|
||
var configuration = serviceProvider.GetService<IConfiguration>(); | ||
var dbContext = serviceProvider.GetService<DatabaseContext>(); | ||
var ticketSerializer = new TicketSerializer(); | ||
var httpClient = new HttpClient(); | ||
var memoryCache = serviceProvider.GetService<IMemoryCache>(); | ||
|
||
var azAdSection = configuration.GetSection(AppSettingsConstants.AzureAdSelection); | ||
var azureAdConfiguration = azAdSection.Get<AzureAdConfiguration>(); | ||
var adClientSecret = Environment.GetEnvironmentVariable(AppSettingsConstants.AdSecretKey); | ||
azureAdConfiguration.ClientSecret = adClientSecret; | ||
|
||
var ticketStore = new TicketStore(dbContext, ticketSerializer, httpClient, azureAdConfiguration, memoryCache); | ||
|
||
serviceCollection.AddSingleton<ITicketStore, TicketStore>(_ => ticketStore); | ||
serviceCollection | ||
.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme) | ||
.Configure<ITicketStore>((o, _) => o.SessionStore = ticketStore); | ||
|
||
return serviceCollection; | ||
} | ||
} | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
\begin{spverbatim} | ||
builder.Services.AddTicketStore(); | ||
\end{spverbatim} |
54 changes: 54 additions & 0 deletions
54
src/code_snippets/07_implementation_background_service.tex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
\begin{spverbatim} | ||
public class RefreshBackgroundService : IHostedService | ||
{ | ||
private readonly DatabaseContext _context; | ||
private readonly TicketStore _ticketStore; | ||
|
||
public RefreshBackgroundService(DatabaseContext context, TicketStore ticketStore) | ||
{ | ||
_context = context; | ||
_ticketStore = ticketStore; | ||
} | ||
|
||
public Task StartAsync(CancellationToken cancellationToken) | ||
{ | ||
var _ = StartRefreshingUserSessionsAsync(cancellationToken); | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
public Task StopAsync(CancellationToken cancellationToken) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
private async Task StartRefreshingUserSessionsAsync(CancellationToken cancellationToken) | ||
{ | ||
while (!cancellationToken.IsCancellationRequested) | ||
{ | ||
var expiringUserSessions = await _context.UserSessions | ||
.Where(x => (x.ExpiresAt > DateTimeOffset.UtcNow && | ||
x.ExpiresAt < DateTimeOffset.UtcNow.AddMinutes(5)) || | ||
x.ExpiresAt < DateTimeOffset.UtcNow) | ||
.ToListAsync(cancellationToken); | ||
|
||
foreach (var userSession in expiringUserSessions) | ||
{ | ||
var differenceBetweenLastAccessAndUtcNow = userSession.DateOfLastAccess | ||
.Subtract(DateTimeOffset.UtcNow) | ||
.Duration(); | ||
|
||
if (differenceBetweenLastAccessAndUtcNow > TimeSpan.FromDays(3)) | ||
{ | ||
await _ticketStore.RemoveAsync(userSession.Id.ToString()); | ||
continue; | ||
} | ||
|
||
await _ticketStore.RenewAsync(userSession.Id.ToString(), null); | ||
} | ||
|
||
await Task.Delay(TimeSpan.FromMinutes(10), cancellationToken); | ||
} | ||
} | ||
} | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
\begin{spverbatim} | ||
public static class HostedServicesDependencyInjection | ||
{ | ||
public static IServiceCollection AddHostedServices(this IServiceCollection serviceCollection) | ||
{ | ||
var serviceProvider = serviceCollection.BuildServiceProvider(); | ||
|
||
var configuration = serviceProvider.GetService<IConfiguration>(); | ||
var databaseContext = serviceProvider.GetService<DatabaseContext>(); | ||
var memoryCache = serviceProvider.GetService<IMemoryCache>(); | ||
|
||
var tickerSerializer = new TicketSerializer(); | ||
var httpClient = new HttpClient(); | ||
|
||
var azAdSection = configuration.GetSection(AppSettingsConstants.AzureAdSelection); | ||
var azureAdConfiguration = azAdSection.Get<AzureAdConfiguration>(); | ||
var adClientSecret = Environment.GetEnvironmentVariable(AppSettingsConstants.AdSecretKey); | ||
azureAdConfiguration.ClientSecret = adClientSecret; | ||
|
||
var tickerStore = new TicketStore( | ||
databaseContext, | ||
tickerSerializer, | ||
httpClient, | ||
azureAdConfiguration, | ||
memoryCache); | ||
|
||
var refreshBackgroundService = new RefreshBackgroundService(databaseContext, tickerStore); | ||
|
||
serviceCollection.AddHostedService(_ => refreshBackgroundService); | ||
|
||
return serviceCollection; | ||
} | ||
} | ||
\end{spverbatim} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
\begin{spverbatim} | ||
builder.Services.AddHostedServices(); | ||
\end{spverbatim} |
Oops, something went wrong.