Google OAuth Data Storage for tokens - c#

Our app needs to let users sync sync their Google calendar on to our internal events module but I'm stuck on doing the authorization and storing it on to our DB. I was following this sample Google OAuth for reference but in that sample it was stated to implement your own DataStore that uses EF, I went ahead and tried it but I can't seem to make it to work and I've been stuck for a couple of weeks now, any help will be greatly appreciated. Thanks!
I had something like this before:
public class TokenDataStore : IDataStore
{
private readonly IUnitOfWork _unitOfWork;
private readonly IUserRepository _userRepository;
private readonly IBusinessRepository _businessRepository;
public TokenDataStore(IUnitOfWork unitOfWork, IUserRepository userRepository, IBusinessRepository businessRepository)
{
this._unitOfWork = unitOfWork;
this._userRepository = userRepository;
this._businessRepository = businessRepository;
}
//Todo: Add IdataStore Members
public static string GenerateStoredKey(string key, Type t)
{
return string.Format("{0}-{1}", t.FullName, key);
}
}
Some more information:
- MVC 4
-.Net 4.5
- EF 5
- Google APIs
EDIT
Ok, so I made some progress this past couple of hours, I was finally able to make my own AppFlowMetaData
private readonly IAuthorizationCodeFlow flow;
private readonly GoogleAPI _API;
private readonly IGoogleAPIRepository _googleAPIRepository;
private readonly IUnitOfWork _unitOfWork;
public AppFlowMetadata(GoogleAPI API, IGoogleAPIRepository googleAPIRepository, IUnitOfWork unitOfWork)
{
this._API = API;
this._googleAPIRepository = googleAPIRepository;
this._unitOfWork = unitOfWork;
this.flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ConfigurationManager.AppSettings["GP_Key"],
ClientSecret = ConfigurationManager.AppSettings["GP_Secret"]
},
Scopes = new[] { CalendarService.Scope.Calendar },
DataStore = new TokenDataStore(_API, _googleAPIRepository, _unitOfWork)
});
}
also my own data store class:
private readonly IGoogleAPIRepository _googleAPIRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly GoogleAPI _model;
public TokenDataStore(GoogleAPI model, IGoogleAPIRepository googleAPIRepository, IUnitOfWork unitOfWork)
{
this._googleAPIRepository = googleAPIRepository;
this._unitOfWork = unitOfWork;
this._model = model;
}
Now I encounter a problem during the call back. So, when a user signs in using their account, in my data store class, I save the token as a string in the database and when the code gets to the part where I get the token from my model the data type passed is a string and not the usual Token Response. Here is the complete code for my data store:
public class TokenDataStore : IDataStore
{
private readonly IGoogleAPIRepository _googleAPIRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly GoogleAPI _model;
public TokenDataStore(GoogleAPI model, IGoogleAPIRepository googleAPIRepository, IUnitOfWork unitOfWork)
{
this._googleAPIRepository = googleAPIRepository;
this._unitOfWork = unitOfWork;
this._model = model;
}
public System.Threading.Tasks.Task StoreAsync<T>(string key, T value)
{
var serialized = NewtonsoftJsonSerializer.Instance.Serialize(value);
if(serialized.Contains("access_token"))
{
_model.TokenString = serialized;
_googleAPIRepository.Save(_model, EntityState.Modified);
_unitOfWork.Commit();
}
return TaskEx.Delay(0);
}
public System.Threading.Tasks.Task DeleteAsync<T>(string key)
{
_model.TokenString = "";
_googleAPIRepository.Save(_model, EntityState.Modified);
_unitOfWork.Commit();
return TaskEx.Delay(0);
}
public Task<T> GetAsync<T>(string key)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
try
{
tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize<T>(_model.TokenString));
}
catch(Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}
public System.Threading.Tasks.Task ClearAsync()
{
return TaskEx.Delay(0);
}
}

Kevin, it is not clear what isn't working..
Could be:
Tokens are not being stored in your database
Authentication is not happening when accessing users resources at later time
I assume it is the latter, so there are two challenges you need to fix:
Token expiration is making Database invalid
Tokens are not being fed back into googleauthenticator when accessing users resources
Things you need to do are to ensure you allow the access to be offline so the server has access to the resources while the user is not present or session out of scope. Read chapter "offline access" here how to do this: Using oAuth for WebApplications
Make sure you validate that the tokens are still valid when feeding them back into the authenticator. If not you need to use refresh token to renew authentication. Validating token
You will need to ensure the googleauthenticator is called with the tokens from you Database to ensure your code has access.
Hope I assumed the correct problem... ;)

You can use the FileDataStore (https://code.google.com/p/google-api-dotnet-client/source/browse/Src/GoogleApis.DotNet4/Apis/Util/Store/FileDataStore.cs), that stores its data in files and not EntityFramework.
Regarding your question - I didn't get it, what is your exact problem?
You need more details on how to inject that data store into your flow object? Or you help with EF? ... Please provide more information.

Try storing an object with the following parameters in your Entity Framework Repository.
public string access_token { get; set; }
public string token_type { get; set; }
public long? expires_in { get; set; }
public string refresh_token { get; set; }
public string Issued { get; set; }
I'm retrieving my tokens from an Web Api which contains an Entity Framework Repository. I retrieve my object as a JSON string. Use Google.Api.Json.NewtonsoftJsonSerializer to deserialize the Json string.
public Task<T> GetAsync<T>(string key)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
try
{
string dataStoreView = this.httpClient.Get(uri).ResponseBody;
tcs.SetResult(Google.Apis.Json.NewtonsoftJsonSerializer.Instance.Deserialize<T>(dataStoreView));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
return tcs.Task;
}

Related

Using AuthenticationStateProvider in C# class instead of .razor page

I have a web application that uses Oauth 2.0 authorization to login.
Getting the claims from AuthenticationStateProvider in a Blazor page is pretty simple:
#inject AuthenticationStateProvider AuthState
#code
{
protected override async Task OnInitializedAsync()
{
authenticationState = await AuthState.GetAuthenticationStateAsync();
username = authenticationState.User.FindFirst("claim type name here");
}
}
Then I can display the username easily in the .razor document HTML part.
However, I want to get this username and use it when calling an API on behalf of that username. I just can't understand how to use the AuthenticationStateProvider and inject the stuff I need in a normal C# class that's not a blazor page.
I found a solution for getting the claims from HttpContext and it's like this:
public class UserIdentity
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserIdentity(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
public string GetUsername()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
return username;
}
}
But I don't understand how to use that class with the constructor elsewhere. I would have to pass a IHttpContextAccessor when creating an instance of the class and I can't make sense of it.
EDIT: Changed some of this and tried a new approach:
public class UserIdentity
{
private readonly AuthenticationStateProvider _authenticationStateProvider;
public UserIdentity(AuthenticationStateProvider _authenticationStateProvider)
{
this._authenticationStateProvider = _authenticationStateProvider;
}
public async Task<string> GetUsername()
{
var authstate = await _authenticationStateProvider.GetAuthenticationStateAsync();
System.Security.Claims.Claim username = authstate.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
return username.Value;
}
}
Returns null reference exception. But the real problem is I don't know how to use the UserIdentity. I don't see the point of constructor injection when I have to pass a value.
UserIdentity identity = new UserIdentity(... AuthenticationStateProvider? ...);

Circular dependency in ASP.NET Core

For school we have to write our own WebApi using the .NET Entity Core Framework. I've written my api but when I tried to use it in swagger, it always returned a HTTP 500 error: internal server error. I downloaded Fiddler to start debugging and came across a circular dependency error in my repository but I can't figure out where this would take place.
The interface (for mock testing)
public interface IVisitorRepository
{
Visitor GetBy(string email);
void AddVisitor(Visitor visitor);
void SaveChanges();
}
The concrete class
public class VisitorRepository : IVisitorRepository
{
private readonly ApplicationDbContext _context;
private readonly DbSet<Visitor> _visitors;
public VisitorRepository(ApplicationDbContext context, IVisitorRepository visitorRepository)
{
_context = context;
_visitors = _context.Visitors;
}
public void AddVisitor(Visitor visitor)
{
_visitors.Add(visitor);
}
public Visitor GetBy(string email)
{
return _visitors.SingleOrDefault(v => v.Email == email);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
I've scoped it in my pipeline.
It's a JWT token based login and register api (that's what we need to make) and here's my register method (the method I'm testing)
[AllowAnonymous]
[HttpPost("register")]
public async Task<ActionResult<String>> Register(RegisterDTO model)
{
IdentityUser user = new IdentityUser { UserName = model.Email, Email = model.Email };
Visitor visitor = new Visitor(model.FirstName + " " + model.LastName, model.Email, model.PhoneNumber, model.Country);
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_visitorRepository.AddVisitor(visitor);
_visitorRepository.SaveChanges();
string token = GetToken(user);
return Created("", token);
}
return BadRequest();
}
The exception:
InvalidOperationException: A circular dependency was detected for the service of type 'DigitizedApi.Models.Repositories.IVisitorRepository'. DigitizedApi.Models.Repositories.IVisitorRepository(DigitizedApi.Data.Repositories.VisitorRepository) -> DigitizedApi.Models.Repositories.IVisitorRepository
Problem is your VisitorRepository (which implements IVisitorRepository) has a dependency on IVisitorRepository itself.
Actually it should be as follows:
public class VisitorRepository : IVisitorRepository
{
private readonly ApplicationDbContext _context;
private readonly DbSet<Visitor> _visitors;
public VisitorRepository(ApplicationDbContext context)
{
_context = context;
_visitors = _context.Visitors;
}
.........
}

Glabal variable not dependant on session

Is there a way to have a global variable that is not session dependent?
I want to have a list of all the SignalR connections and the user ID form the db.
So far I have something like this :
public class SignalRUsersService : ISignalRUsersService
{
private int userId;
private readonly IHttpContextAccessor _accessor;
public List<ConnectedSignalR> SignalRUsers;
public SignalRUsersService( IHttpContextAccessor accessor)
{
_accessor = accessor;
try
{
userId = Convert.ToInt32(_accessor.HttpContext.Session.GetString("uID"));
}
catch
{
userId = 0;
}
SignalRUsers = new List<ConnectedSignalR>();
}
public void AddSignalRUser(string ConnID)
{
SignalRUsers.Add(new ConnectedSignalR()
{
ConnID = ConnID,
UserID = userId
});
}
public void RemoveSignalRUser(string ConnID)
{
var usr = SignalRUsers.Where(a => a.ConnID == ConnID).FirstOrDefault();
if (usr != null)
{
SignalRUsers.Remove(usr);
}
}
public List<ConnectedSignalR> GetSignalRUsers()
{
return SignalRUsers;
}
}
The problem is every time I use the interface it seems it's re-initializing my SignalRUsers list. And even if I push the data in the list I find it null when I need it.
Is SignalRUsers session dependent? Or is it just a matter of not using the interface in a right way?
Any help is much appreciated.
SignalR hubs are transient, which means a new instance is created when a method call is received from a client (see: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server).
You are probably injecting this service into a hub class, which means you can use dependency injection configuration to control the lifetime of your service class.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISignalRUsersService, SignalRUsersService>();
}

Tenant session is lost in IRealTimeNotifier

We are trying to achieve a scenario where user can subscribe to notifications via channels.
For example, user can subscribe to notifications via Email, SMS, Real Time Notifications, etc.
For now, in order to achieve email notifications, I am implementing the IRealTimeNotifier in this way:
public class EmailNotifier : IRealTimeNotifier, ITransientDependency
{
private readonly IEmailSender _emailSender;
private readonly ISettingManager _settingManager;
public IAbpSession AbpSession { get; set; }
public EmailNotifier(IEmailSender emailSender, ISettingManager settingManager, IAbpSession abpSession)
{
this._emailSender = emailSender;
this._settingManager = settingManager;
this.AbpSession = NullAbpSession.Instance;
}
public async Task SendNotificationsAsync(UserNotification[] userNotifications)
{
List<Task> sendEmails = new List<Task>();
string fromAddress = this._settingManager.GetSettingValueForTenant(EmailSettingNames.Smtp.UserName, Convert.ToInt32(this.AbpSession.TenantId));
foreach (UserNotification notification in userNotifications)
{
notification.Notification.Data.Properties.TryGetValue("toAddresses", out object toAddresses);
notification.Notification.Data.Properties.TryGetValue("subject", out object sub);
notification.Notification.Data.Properties.TryGetValue("body", out object content);
notification.Notification.Data.Properties.TryGetValue("toAddresses", out object toAddresses);
List<string> toEmails = toAddresses as List<string> ;
string subject = Convert.ToString(sub);
string body = Convert.ToString(content);
toEmails.ForEach(to =>
{
sendEmails.Add(this._emailSender.SendAsync(fromAddress, to, subject, body));
});
}
await Task.Run(() =>
{
sendEmails.ForEach(async task =>
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception)
{
// TODO #1: Add email to background job to be sent later.
// TODO #2: Add circuit breaker to email sending mechanism
/*
Email sending is failing because of two possible reasons.
1. Email addresses are wrong.
2. SMTP server is down.
3. Break the circuit while the SMTP server is down.
*/
// TODO #3 (Optional): Notify tenant admin about failure.
// TODO #4: Remove throw statement for graceful degradation.
throw;
}
});
});
}
}
The problem is with the IAbpSession, whether I inject it via property or constructor, at the time of execution of this notifier, the response has already been returned and the TenantId in this session is null, so the email is being sent with host configurations and not tenant configuration.
Similarly, I need to implement IRealTimeNotifier for SMS. I think I can reuse the SignalRRealTimeNotifier from ABP, but the tenantId in session is being set to null.
This is where the publisher is being called:
public class EventUserEmailer : IEventHandler<EntityCreatedEventData<Event>>
{
public ILogger Logger { get; set; }
private readonly IEventManager _eventManager;
private readonly UserManager _userManager;
private readonly IAbpSession _abpSession;
private readonly INotificationPublisher _notiticationPublisher;
public EventUserEmailer(
UserManager userManager,
IEventManager eventManager,
IAbpSession abpSession,
INotificationPublisher notiticationPublisher)
{
_userManager = userManager;
_eventManager = eventManager;
_notiticationPublisher = notiticationPublisher;
_abpSession = abpSession;
Logger = NullLogger.Instance;
}
[UnitOfWork]
public virtual void HandleEvent(EntityCreatedEventData<Event> eventData)
{
// TODO: Send email to all tenant users as a notification
var users = _userManager
.Users
.Where(u => u.TenantId == eventData.Entity.TenantId)
.ToList();
// Send notification to all subscribed uses of tenant
_notiticationPublisher.Publish(AppNotificationNames.EventCreated);
}
}
Can anybody recommend a better way? Or point anything out that we are doing wrong here.
I have not thought about how to handle DI of these particular notifiers yet. For testing purposes, I have given a named injection in my module like this:
IocManager.IocContainer.Register(
Component.For<IRealTimeNotifier>()
.ImplementedBy<EmailNotifier>()
.Named("Email")
.LifestyleTransient()
);
Current ABP version: 3.7.1
This is the information I have until now. If anything is needed, you can ask in comments.
// Send notification to all subscribed uses of tenant
_notificationPublisher.Publish(AppNotificationNames.EventCreated);
If you publish notifications like this, ABP's default implementation enqueues a background job.
There is no session in a background job.
You can get the tenantId like this:
var tenantId = userNotifications[0].Notification.TenantId;
using (AbpSession.Use(tenantId, null))
{
// ...
}

OwinContext Environment doesn't contains Added element

I am trying to implement multi tenancy in my WebAPI project.
In my Startup.Auth.cs , i am adding selected Tenant object into IOwinContext.
app.Use(async (ctx, next) =>
{
Tenant tenant = GetTenantBasedUrl(ctx.Request.Uri.Host);
if (tenant == null)
{
throw new ApplicationException("tenant not found");
}
ctx.Environment.Add("MultiTenant", tenant);
await next();
}
Where GetTenantBaseUrl function is returnnig us the selected Tenant object.
I have made a class implementing ApiController which i would implement to every controller of mine in order to get the Tenant object.
public class MultiTenantWebApiController : ApiController
{
public Tenant Tenant
{
get
{
object multiTenant;
IDictionary<string, object> dic = HttpContext.Current.GetOwinContext().Environment;
if (!HttpContext.Current.GetOwinContext().Environment.TryGetValue("MultiTenant", out multiTenant))
{
throw new ApplicationException("Could Not Find Tenant");
}
return (Tenant)multiTenant;
}
}
}
In my controller i am getting "MultiTenant" key from OwinContext Environment but i try to fetch the same from ApplicationOAuthProvider class it doesn't show "MultiTenant" key in my OwinContext Environment ie : getEnvironment variable below:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
// some code here
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
**IDictionary getEnvironment = HttpContext.Current.GetOwinContext().Environment;**
// some code
Does anybody know why i am not getting the "MultiTenant" key in OwinContext.Environment of ApplicationOAuthProvider whereas i get it inside my controller ?
Thanks!
I would like to suggest that you can use the context injected to each of your api controllers so that the tenant and its context is visible across the layers. The context provider can be looking something like this
public class ClaimsContextDataProvider : IUserContextDataProvider
{
public Guid UserId
{
get
{
var userId = (Thread.CurrentPrincipal as ClaimsPrincipal)?.FindFirst(ClaimTypes.Sid)?.Value;
return TryGetGuidFromString(userId);
}
}
}
Then registering the context provider in the DI framework [example of using the Autofac is given below]
builder.RegisterType<ClaimsContextDataProvider>().As<IUserContextDataProvider>();
Then have a BaseApiController something like the below snippet
public Guid TenantId { get { return _userContext.TenantId; } }
public BaseApiController(IMapper mapper, IUserContextDataProvider userContext)
{
_mapper = mapper;
_userContext = userContext;
}
Accessing the TenantId property of the BaseApiController inside the derived controllers [CountriesController.cs]
// POST api/countries
public async Task<HttpResponseMessage> PostCountry(CountryRequestModel requestModel)
{
Country country = _mapper.Map<CountryRequestModel, Country>(requestModel);
country.CreatedOn = DateTimeOffset.Now;
country.TenantId = TenantId;
await _countryService.AddAsync(country);
CountryDto countryDto = _mapper.Map<Country, CountryDto>(country);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, countryDto);
response.Headers.Location = GetCountryLink(country.Id);
return response;
}
You can take a look at the sample app and the template given in the below link
Multi-Tenant dev template
Its a little bit deep to explain here, please feel free to read the docs here

Categories

Resources