Reputation: 1965
I am using IdentityServer4 in .Net Core 2.0 and I am successfully generating access tokens and refresh tokens. I just need to be able to "see" the refresh token on the server side when it's being generated, so that I can save it in a database for some specific purposes.
How can I access the refresh token's value while it is being generated on the server?
Upvotes: 1
Views: 5174
Reputation: 3166
According to the comments, I think that this will be a useful solution for you, and for others with your case.
I'm starting with the things around IdentityServer itself. As it is highly recommended to use your own PersistedGrant
store for production environments we need to override the default one.
First - in the Startup.cs:
services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
This will implement their IPersistedGrantStore
interface, with our own PersistedGrantStore
class.
The class itself:
public class PersistedGrantStore : IPersistedGrantStore
{
private readonly ILogger logger;
private readonly IPersistedGrantService persistedGrantService;
public PersistedGrantStore(IPersistedGrantService persistedGrantService, ILogger<PersistedGrantStore> logger)
{
this.logger = logger;
this.persistedGrantService = persistedGrantService;
}
public Task StoreAsync(PersistedGrant token)
{
var existing = this.persistedGrantService.Get(token.Key);
try
{
if (existing == null)
{
logger.LogDebug("{persistedGrantKey} not found in database", token.Key);
var persistedGrant = token.ToEntity();
this.persistedGrantService.Add(persistedGrant);
}
else
{
logger.LogDebug("{persistedGrantKey} found in database", token.Key);
token.UpdateEntity(existing);
this.persistedGrantService.Update(existing);
}
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogWarning("exception updating {persistedGrantKey} persisted grant in database: {error}", token.Key, ex.Message);
}
return Task.FromResult(0);
}
public Task<PersistedGrant> GetAsync(string key)
{
var persistedGrant = this.persistedGrantService.Get(key);
var model = persistedGrant?.ToModel();
logger.LogDebug("{persistedGrantKey} found in database: {persistedGrantKeyFound}", key, model != null);
return Task.FromResult(model);
}
public Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId)
{
var persistedGrants = this.persistedGrantService.GetAll(subjectId).ToList();
var model = persistedGrants.Select(x => x.ToModel());
logger.LogDebug("{persistedGrantCount} persisted grants found for {subjectId}", persistedGrants.Count, subjectId);
return Task.FromResult(model);
}
public Task RemoveAsync(string key)
{
var persistedGrant = this.persistedGrantService.Get(key);
if (persistedGrant != null)
{
logger.LogDebug("removing {persistedGrantKey} persisted grant from database", key);
try
{
this.persistedGrantService.Remove(persistedGrant);
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogInformation("exception removing {persistedGrantKey} persisted grant from database: {error}", key, ex.Message);
}
}
else
{
logger.LogDebug("no {persistedGrantKey} persisted grant found in database", key);
}
return Task.FromResult(0);
}
public Task RemoveAllAsync(string subjectId, string clientId)
{
var persistedGrants = this.persistedGrantService.GetAll(subjectId, clientId);
logger.LogDebug("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}", persistedGrants.Count(), subjectId, clientId);
try
{
this.persistedGrantService.RemoveAll(persistedGrants);
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogInformation("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}: {error}", persistedGrants.Count(), subjectId, clientId, ex.Message);
}
return Task.FromResult(0);
}
public Task RemoveAllAsync(string subjectId, string clientId, string type)
{
var persistedGrants = this.persistedGrantService.GetAll(subjectId, clientId, type);
logger.LogDebug("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}, grantType {persistedGrantType}", persistedGrants.Count(), subjectId, clientId, type);
try
{
this.persistedGrantService.RemoveAll(persistedGrants);
}
catch (DbUpdateConcurrencyException ex)
{
logger.LogInformation("exception removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}, grantType {persistedGrantType}: {error}", persistedGrants.Count(), subjectId, clientId, type, ex.Message);
}
return Task.FromResult(0);
}
}
As you can see in it I have an interface and the logger.
The IPersistedGrantService
interface:
public interface IPersistedGrantService
{
void Add(PersistedGrantInfo persistedGrant);
void Update(PersistedGrantInfo existing);
PersistedGrantInfo Get(string key);
IEnumerable<PersistedGrantInfo> GetAll(string subjectId);
IEnumerable<PersistedGrantInfo> GetAll(string subjectId, string clientId);
IEnumerable<PersistedGrantInfo> GetAll(string subjectId, string clientId, string type);
void Remove(PersistedGrantInfo persistedGrant);
void RemoveAll(IEnumerable<PersistedGrantInfo> persistedGrants);
}
As you can see, There is an object called PersistedGrantInfo
. This is my DTO that I use for the mapping between the db entity, and the IDS4 entity (you are not forced to use it, but I'm doing it for a better abstraction).
This Info object is mapped to the IDS4 entity with AutoMapper:
public static class PersistedGrantMappers
{
internal static IMapper Mapper { get; }
static PersistedGrantMappers()
{
Mapper = new MapperConfiguration(cfg => cfg.AddProfile<PersistedGrantMapperProfile>())
.CreateMapper();
}
/// <summary>
/// Maps an entity to a model.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
public static PersistedGrant ToModel(this PersistedGrantInfo entity)
{
return entity == null ? null : Mapper.Map<PersistedGrant>(entity);
}
/// <summary>
/// Maps a model to an entity.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
public static PersistedGrantInfo ToEntity(this PersistedGrant model)
{
return model == null ? null : Mapper.Map<PersistedGrantInfo>(model);
}
/// <summary>
/// Updates an entity from a model.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="entity">The entity.</param>
public static void UpdateEntity(this PersistedGrant model, PersistedGrantInfo entity)
{
Mapper.Map(model, entity);
}
}
And the mapper profile:
public class PersistedGrantMapperProfile:Profile
{
/// <summary>
/// <see cref="PersistedGrantMapperProfile">
/// </see>
/// </summary>
public PersistedGrantMapperProfile()
{
CreateMap<PersistedGrantInfo, IdentityServer4.Models.PersistedGrant>(MemberList.Destination)
.ReverseMap();
}
}
Going back to the IPersistedGrantService
- the implementation is up to you. Currently as a DB entity I have an exact copy of the IDS4 entity:
public class PersistedGrant
{
[Key]
public string Key { get; set; }
public string Type { get; set; }
public string SubjectId { get; set; }
public string ClientId { get; set; }
public DateTime CreationTime { get; set; }
public DateTime? Expiration { get; set; }
public string Data { get; set; }
}
But according to your needs, you can do something different (store this data in different table, use different column names etc.). Then in my service implementation, I'm just using the data that comes from the `IPersistedGrantStore' implementation, and I'm CRUD-ing the entities in my db context.
As a conclusion - the main thing here is to override\implement their IPersistedGrantStore
interface according to your needs. Hope that this helps.
Upvotes: 7