How to invalidate 'expired' Firebase Instance ID Token - firebase

AFAIK, the Firebase Instance Token will be refreshed under the following 4 conditions:
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
Suppose a user is using Token A as his 'FCM address'. Every time when he logs in the app, he will register the Token A to the Firestore along with this user's UUID so user-specific cloud message can be sent to him. When he logs out, the system will fire a request to firestore for removing the token A record.
Now, when the user reinstalls the app, the instance id is refreshed and a new Token B is generated. The Token A becomes useless. Unfortunately, if the user does not log out before the uninstallation, token A will stay in the firestore forever.
Any workaround or wiser way to handle this case?

Keeping your token registry up to date requires two steps:
Remove outdated tokens from your application code.
Check for outdated tokens and remove them when you send messages.
Your approach of removing a token that is no longer used, is #1.
The second step though is to remove tokens from your registry/database when you get a messaging/invalid-registration-token or messaging/registration-token-not-registered response when trying to send a message to it. The functions-samples repo contains a great example of this:
admin.messaging().sendToDevice(tokens, payload).then((response) => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
// TODO: remove the token from your registry/database
}
}
});
});
The above code uses the Firebase Admin SDK for Node.js, but the same logic could also be applied to other platforms or when sending messages through the HTTPS endpoints.

As Frank mentioned in his answer you can remove them when sending messages and getting not registered error.
Here how I delete outdated registration tokens when registering a new one using C#.
First of all using Instance ID API I get token info as following:
public async Task<FCMTokenInfo> GetTokenInfoAsync(string token)
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://iid.googleapis.com");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", String.Format("key={0}", "your-authorization-key"));
var uri = $"/iid/info/{token}";
var httpResponse = await client.GetAsync(uri);
var responseStr = await httpResponse.Content.ReadAsStringAsync();
if (httpResponse.StatusCode != HttpStatusCode.OK)
{
//log 400 bad request and do whatever you want
}
var result = JsonConvert.DeserializeObject<FCMTokenInfo>(responseStr);
return result;
}
catch (Exception ex)
{
//log the exception
throw;
}
}
FCMTokenInfo.cs
public class FCMTokenInfo
{
public string Application { get; set; }
public string Subtype { get; set; }
public string Scope { get; set; }
public string AuthorizedEntity { get; set; }
public string Platform { get; set; }
}
And then inside the service that saves registration tokens inside the database:
//this method gets called when a new token is sent by the javascript web app
public async Task AddTokenAsync(Guid accountId, string token)
{
try
{
//getting TokenInfo of the current token(or refreshed one for that app)
var fcmTokenInfo = await firebaseServices.GetTokenInfoAsync(token);
//adding the current token
dbContext.FcmRegisterationTokens.Add(new FcmRegisterationToken
{
Token = token,
AccountId = accountId,
AddingDate = DateTimeOffset.UtcNow,
Application = fcmTokenInfo.Application,
Subtype = fcmTokenInfo.Subtype,
AuthorizedEntity = fcmTokenInfo.AuthorizedEntity,
Scope = fcmTokenInfo.Scope,
Platform = fcmTokenInfo.Platform
});
var outdatedTokens = await dbContext.FcmRegisterationTokens
.Where(x => x.AccountId == accountId
&& x.Application == fcmTokenInfo.Application
&& x.Platform == fcmTokenInfo.Platform
).ToListAsync();
//remove them
dbContext.FcmRegisterationTokens.RemoveRange(outdatedTokens);
dbContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}

Related

How to unsubscribe email from AWS SNS Topic?

I have an endpoint that subscribes the specified email to my SNS topic:
[HttpPost("subscriptions/{email}")]
public async Task SubscribeEmail(string email)
{
try
{
var request = new SubscribeRequest()
{
TopicArn = AwsServicesConstants.SenderTopicArn,
ReturnSubscriptionArn = true,
Protocol = "email",
Endpoint = email,
};
var response = await _snsClient.SubscribeAsync(request);
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex}");
}
}
How can I unsubscribe given email from that topic with just a specified email like this
[HttpDelete("subscriptions/{email}")]
public async Task<UnsubscribeResponse> UnsubscribeEmail(string email)
{
var request = new UnsubscribeRequest(email);
var response = await _snsClient.UnsubscribeAsync(request);
return response;
}
Actually, unsubscription is not working because UnsubscribeRequest requires only subscriptionArn, and not the email
You will need to identify the subscription (once subscribed) by calling ListSubscriptionsByTopic, looking for the Endpoint that matches the desired email address. You could then extract the ARN and use it when calling Unsubscribe.
You can write app logic to get the ARN value using the email address. Here is a C# example that shows you the logic for this use case using the AWS SDK for .NET.
public async Task<string> UnSubEmail(string email)
{
var client = new AmazonSimpleNotificationServiceClient(RegionEndpoint.USEast2);
var arnValue = await GetSubArn(client, email);
await RemoveSub(client, arnValue);
return $"{email} was successfully deleted!";
}
public static async Task<string> GetSubArn(IAmazonSimpleNotificationService client, string email)
{
var request = new ListSubscriptionsByTopicRequest();
request.TopicArn = TopicArn;
var subArn = string.Empty;
var response = await client.ListSubscriptionsByTopicAsync(request);
List<Subscription> allSubs = response.Subscriptions;
// Get the ARN Value for this subscription.
foreach (Subscription sub in allSubs)
{
if (sub.Endpoint.Equals(email))
{
subArn = sub.SubscriptionArn;
return subArn;
}
}
return string.Empty;
}
public static async Task<string> RemoveSub(IAmazonSimpleNotificationService client, string subArn)
{
var request = new UnsubscribeRequest();
request.SubscriptionArn = subArn;
await client.UnsubscribeAsync(request);
return string.Empty;
}
You can find full .NET Example in the AWS Code Lib:
Build a publish and subscription application that translates messages

(Blazor-server-side / SignalR / Net6) HubConnectionContext loses user in HttpContext the second time it's called

I am trying to send a message with SignalR to a specific user.
I implemented the default project authentication with Blazor Server side and Net6.
I can log in / log out / register.
I implemented the IUSerIdProvider Interface to get the UserId.
The first time I launch the app, I can retrieved the user (from connection.GetHttpContext(); or connection.User.FindFirstValue(ClaimTypes.Name); but when I navigate to an other page and call the hub again, the HubConnectionContext loses my User and all his informations.
If I force the id with a constant string it works but why do I lose the informations the second time ?
I don't know if I need to use cookies because the first time I have informations.
// CustomUserIdProvider.cs
public class CustomUserIdProvider : IUserIdProvider
{
public string? GetUserId(HubConnectionContext connection)
{
var httpContext = connection.GetHttpContext();
var userId = connection.User.FindFirstValue(ClaimTypes.Name);
if (string.IsNullOrWhiteSpace(userId))
return string.Empty;
return userId;
}
}
// Program.cs
-----
builder.Services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
-----
app.UseAuthentication();
app.UseAuthorization();
// SignalR.razor (where I test to receive / send a message and here I lost the informations)
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/notifyhub"))
.Build();
hubConnection.On<int, string>("ReceiveMessage", (id, message) =>
{
var encodedMsg = $"{id}: {message}";
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
authMessage = $"{user.Identity.Name} is authenticated.";
claims = user.Claims;
surnameMessage =
$"Surname: {user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value}";
await hubConnection.SendAsync("Send", user.Identity.Name, 1, "Message envoyé");
}
}

Google Authentication get access token using refresh token

I am using Push Notifications API for integrating client's calendar event. I have created channel also. But I am not able to get refresh token while authentication. I need to get user consent from UI and based on the verification code I am getting access token and refresh token. But when I am trying to get access token using existing refresh token, it is asking for client Id and secret. As I am just using consent from user I do not have client Id and client secret for each user. How can I resolve this for my application?
I am using below code for authentication
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT)
throws IOException {
// Load client secrets.
InputStream in = CalendarQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
Here is how I am calling calender API
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Credential credential = getCredentials(HTTP_TRANSPORT);
String accessToken = credential.getAccessToken();
Calendar service =
new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, googleCredential)
.setApplicationName(APPLICATION_NAME)
.build();
System.out.println(accessToken);
// List the next 10 events from the primary calendar.
DateTime now = new DateTime(System.currentTimeMillis());
Events events =
service
.events()
.list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute();
List<Event> items = events.getItems();
if (items.isEmpty()) {
System.out.println("No upcoming events found.");
} else {
System.out.println("Upcoming events");
for (Event event : items) {
DateTime start = event.getStart().getDateTime();
if (start == null) {
start = event.getStart().getDate();
}
// System.out.printf("%s %s (%s)\n", event.getId(), event.getSummary(), event.toString());
}
}
}
}

Azure Notification Hub - "The token obtained from the token provider is wrong"

Using the Notifications REST API and JavaScript, we are subscribing our Progressive Web App through FCM and then calling the registrations endpoint to register on our ANH
The registration completes fine and we can see the registration on our hub with the correct platform and a populated PNS Identifier
When we attempt to send a test message to all registered devices, we get the following error in ANH
The token obtained from the token provider is wrong
We have tried sending the entire endpoint object returned by Firebase, just the subscriptionId and various other combinations
Does the error message mean that we have subscribed using the wrong key pair or is the format of the token incorrect? There is nowhere that shows an example of what the GcmRegistrationId format should be when registering with the registrations endpoint
You can use below approach to register your Devices:
In case of GCM follow this approach:
use Nuget package for Notification Hubs.
For DeviceRegistration.cs
public class DeviceRegistration
{
public string Platform { get; set; }
public string Handle { get; set; }
public string[] Tags { get; set; }
}
For NotificationClient.cs
using Microsoft.Azure.NotificationHubs; // Namespace to be used
// Use below method to get registrationID
public async Task<string> GetRegistrationID(NotificationHubClient Hub, string handle = null)
{
string newRegistrationId = null;
// make sure there are no existing registrations for this push handle (used for iOS and Android)
if (handle != null)
{
var registrations = await Hub.GetRegistrationsByChannelAsync(handle, 100);
foreach (RegistrationDescription registration in registrations)
{
if (newRegistrationId == null)
{
newRegistrationId = registration.RegistrationId;
}
else
{
await Hub.DeleteRegistrationAsync(registration);
}
}
}
if (newRegistrationId == null)
newRegistrationId = await Hub.CreateRegistrationIdAsync();
return newRegistrationId;
}
// Use below method to upsert registration to azure
public async Task UpsertRegistration(string registrationid, DeviceRegistration deviceUpdate, NotificationHubClient Hub)
{
string[] tags = { "abc","def" }; // These are used to send notifications
DeviceRegistration deviceRegistration = new DeviceRegistration
{
Handle = newDeviceToken, // Device token given by Firebase
Platform = "gcm", // Specify gcm for android and "apns" for ios
Tags = tags
};
RegistrationDescription registration
= new GcmRegistrationDescription(deviceRegistration.Handle);
registration.RegistrationId = registrationid;
// add check if user is allowed to add these tags
registration.Tags = new HashSet<string>();
foreach (string tag in deviceUpdate.Tags)
{
registration.Tags.Add(tag);
}
await Hub.CreateOrUpdateRegistrationAsync(registration);
}

MVC 5 Web API with Facebook access token to RegisterExternal without need of Cookie

Setup:
New MVC5 Project with just Web API. Added Facebook AppId and Secret.
I can get Token for my Web API from Token endpoint by passing in UserName and Password. Then use that token for further calls.
BUT
I want to register new users with the help of Facebook SDK in iOS app.
I am using Facebook SDK to get Access Token. (Assume at this point, I have an Access Token).
Next thing I know is to call api/Account/RegisterExternal endpoint by passing this token in Authorization header with Bearer [Access Token] but this result in 500 server error.
I guess I know the reason, Cookie is missing. I made the same call with a cookie from Fidler and it worked. (Cookie is received by going to URL provided by ExternalLogins endpoint).
As cookie is missing await Authentication.GetExternalLoginInfoAsync(); inside the RegisterExternal action returns null.
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
I don't want to make 3 calls to my Web API to ask for external logins and then goto that URL and authenticate in a Web Browser for Facebook access token and then call the RegisterExternal endpoint with that access token and Cookie that I need to collect between these calls.
As I said I didn't change anything in template except the Facebook Ids. Still the code is as below.
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx");
}
}
as far as I know, Web API doesn't need Cookie and that appears true when I have Local Token from Token endpoint but why does it require Cookie in the first place when doing ExternalRegister
WebApiConfig class looks like this and shouldn't config.SuppressDefaultHostAuthentication(); avoid any Cookie needs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I don't know if I am missing the point here.. My intentions are to not need to use web browser in a native iOS app for the token. That is Facebook SDK to get access token and using that call RegisterExternal to get the Local Token and create that users Identity.
I did my homework and I am stuck on this thought.
Thoughts appreciated!
I was mistaken that it accepts the Social Token with cookie!
It doesn't accept any External Token directly.
The thing is.. MVC 5 is taking care of everything for us, i.e. collecting token from Social Medias and validating/processing it. After that it generates a local token.
The RegisterExternal method also requires cookies to be maintained, the solution does not.
I have written a blog post which will explain in detail. Added the straight forward answer below. I aimed to make it blend and feel integral part of Login/Signup flow of default MVC Web API to make sure its easy to understand.
After the below solution, Authorize attribute must be as below to work or you will get Unauthorized response.
[Authorize]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)]
Use ExternalBearer if you want to allow only Tokens to use API, use ApplicationCookie if you want to allow only Logged cookie to use API i.e. from a website. User both if you want to allow the API for both.
Add this action to AccountController.cs
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[AllowAnonymous]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = await ExternalLoginData.FromToken(model.Provider, model.Token);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != model.Provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return InternalServerError();
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
ClaimsIdentity identity = null;
IdentityResult result;
if (hasRegistered)
{
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
else
{
user = new ApplicationUser() { Id = Guid.NewGuid().ToString(), UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.Email,
Login = new UserLoginInfo(model.Provider, externalLogin.ProviderKey)
};
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
JObject token = new JObject(
new JProperty("userName", user.UserName),
new JProperty("id", user.Id),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
new JProperty(".issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty(".expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
}
Add this helper method to ExternalLoginData class in helper region in AccountController.cs
public static async Task<ExternalLoginData> FromToken(string provider, string accessToken)
{
string verifyTokenEndPoint = "", verifyAppEndpoint = "";
if (provider == "Facebook")
{
verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}", accessToken);
verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", accessToken);
}
else if (provider == "Google")
{
return null; // not implemented yet
//verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
HttpClient client = new HttpClient();
Uri uri = new Uri(verifyTokenEndPoint);
HttpResponseMessage response = await client.GetAsync(uri);
ClaimsIdentity identity = null;
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
dynamic iObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
if (provider == "Facebook")
{
if (appObj["id"] != Startup.facebookAuthOptions.AppId)
{
return null;
}
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, iObj["id"].ToString(), ClaimValueTypes.String, "Facebook", "Facebook"));
}
else if (provider == "Google")
{
//not implemented yet
}
}
if (identity == null)
return null;
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer) || String.IsNullOrEmpty(providerKeyClaim.Value))
return null;
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
return null;
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
and finally, the RegisterExternalTokenBindingModel being used by the action.
public class RegisterExternalTokenBindingModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Token")]
public string Token { get; set; }
[Required]
[Display(Name = "Provider")]
public string Provider { get; set; }
}
Yes, we pass the email along with Token details while registering, this will not cause you to change the code when using Twitter, as Twitter doesn't provide users email. We verify token comes from our app. Once email registered, hacked or somebody else's token cannot be used to change email or get a local token for that email as it will always return the local token for the actual user of the Social Token passed regardless of the email sent.
RegisterExternalToken endpoint works to get token in both ways i.e. register the user and send the Local token or if the user already registered then send the token.
Before everything, this is NOT A FULL Answer, this is just a note or an addition for the answer to avoid some problems which could cost you handful of days (in my case 3 days)
The previous answer is the full answer it just lacks from one thing, which is the following:
if you specified a role for the Authorize attribute, for example [Authorize("UserRole")] , the previous setup will still give you 401 error because the solution does not set the RoleClaim
and to solve this problem you have to add this line of code to the RegisterExternalToken method
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, "UserRole"));

Resources