Redirecting with credentials to another website - asp.net

I have two websites A and B. On A I have the login option and if the user is authenticated I need to send it to B with same credentials. I have access to the code of both websites. My first approach was trying to log the user on A and figure out how could I make a Post to a action method on a controller on B(this is a recurrent question here in stackoverflow) I found this website and I don't know if it is useful: http://www.codeproject.com/Articles/833007/Redirect-and-POST-in-ASP-NET-MVC. My second approach is putting all the data that I wanted to submit to B in Browser Session, call a Redirect to B and then in a method("GET") try to read all that data and check if I can proceed with the logic on B.
I want to know which is the best approach to make this happen, and also if the later is wrong or not.
I have this code on my website A:
[HttpPost]
public ActionResult Login(LoginModel user)
{
//hardcoding InternalUser
user.AccountType = ((int)AccountTypeEnum.Internal).ToString();
var validator = new LoginModelValidator();
var result = validator.Validate(user);
if (!result.IsValid)
{
return View((LoginModelDecorator) user);
}
var service = new AuthenticationServiceAgent(user.Username, user.Password);
var securityService = new SecurityServiceAgent(service.GeToken());
var state = securityService.ProcessAccount(service.GeToken() != null, user.Username);
if (state == (int)UserAccessEnum.Processed)
{
var type = securityService.GetAccountTypeByUser(user.Username);
//CHeck user type
var accountType = Enum.GetName(typeof(AccountTypeEnum), int.Parse(user.AccountType));
var types = type.Split(',').Select(n => n.Split(':')[0]).ToList();
var containsTheUserType = user.AccountType == "1"
? types.Contains("XXX") || types.Contains(accountType)
: types.Contains(accountType);
if (containsTheUserType)
{
//var cPrincipal = service.GetClaims();
var claims = securityService.GetIdentity().Select(claim => new Claim(claim.ClaimType, claim.Value)).ToList();
if (claims.Count != 0)
{
var cPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Custom"));
type.Split(',')
.ToList()
.ForEach(
ty =>
cPrincipal.Identities.First()
.AddClaim(new Claim("http://claims/custom/accounttype", ty)));
var token = new SessionSecurityToken(cPrincipal)
{
IsReferenceMode = true
};
FederatedAuthentication.WSFederationAuthenticationModule.SetPrincipalAndWriteSessionToken(
token, true);
Session["SEC_TOKEN"] = service.GeToken();
//Do I need to post?: SecurityToken, types and claims
//here is where I am redirecting the user
return Redirect("http://localhost:12345/Account/Login");
}
ModelState.AddModelError("LoginError", "Invalid Username or Password!");
}
ModelState.AddModelError("AccountTypeError", "You don't have access");
}
switch (state)
{
case (int)UserAccessEnum.BlockedAccount:
ModelState.AddModelError("StateError", "Your account is Blocked");
break;
case (int)UserAccessEnum.ChangePassword:
ModelState.AddModelError("StateError", "You need to change your password");
break;
case (int)UserAccessEnum.NoProcessed:
ModelState.AddModelError("StateError", "Error, please contact the system administrator");
break;
}
return View((LoginModelDecorator) user);
}
In my website B:
public ActionResult Login()
{
List<Claim> claims = //I need to get the claims from somewhere SESSION?
var type = //type used in the other Loging method
var securityToken = //securityToken used on the other Login method
if (claims.Count != 0)
{
var cPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Custom"));
type.Split(',')
.ToList()
.ForEach(
ty =>
cPrincipal.Identities.First()
.AddClaim(new Claim("http://claims/custom/accounttype", ty)));
var token = new SessionSecurityToken(cPrincipal)
{
IsReferenceMode = true
};
FederatedAuthentication.WSFederationAuthenticationModule.SetPrincipalAndWriteSessionToken(
token, true);
Session["SEC_TOKEN"] = securityToken;
return RedirectToAction("Index", "Home");
}
return null;
}
Any idea of how to complete the gaps that I have?

Related

Obtain user email from Mastodon API via OAuth

Is it possible to obtain user email through Mastodon Api? I'm working on adding OAuth authentication via Mastodon Api but only seem to get "id" and "display_name" using "/api/v1/accounts/verify_credentials" endpoint. I do not see a property returned for email so currently just using "acct" parameter. I'm using both "read:accounts" and "admin:read:accounts" scopes. This is for an NetCore application.
builder.Services.AddAuthentication()
.AddMicrosoftAccount("Microsoft", "Microsoft", microsoftOptions =>
{
microsoftOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
microsoftOptions.ClientId = _appSettings.Authentication.Microsoft.ClientId;
microsoftOptions.ClientSecret = _appSettings.Authentication.Microsoft.ClientSecret;
})
.AddGoogle("Google", "Google", googleOptions =>
{
googleOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
googleOptions.ClientId = _appSettings.Authentication.Google.ClientId;
googleOptions.ClientSecret = _appSettings.Authentication.Google.ClientSecret;
})
.AddGitHub("GitHub", "GitHub", githubOptions =>
{
githubOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
githubOptions.ClientId = _appSettings.Authentication.GitHub.ClientId;
githubOptions.ClientSecret = _appSettings.Authentication.GitHub.ClientSecret;
})
.AddOAuth("Fosstodon", "Fosstodon", fosstodonOptions =>
{
fosstodonOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
fosstodonOptions.ClientId = _appSettings.Authentication.Fosstodon.ClientId;
fosstodonOptions.ClientSecret = _appSettings.Authentication.Fosstodon.ClientSecret;
fosstodonOptions.CallbackPath = new PathString("/signin-fosstodon");
fosstodonOptions.AuthorizationEndpoint = _appSettings.Authentication.Fosstodon.AuthorizationEndpoint;
fosstodonOptions.TokenEndpoint = _appSettings.Authentication.Fosstodon.TokenEndpoint;
fosstodonOptions.UserInformationEndpoint = _appSettings.Authentication.Fosstodon.UserInformationEndpoint;
fosstodonOptions.SaveTokens = true;
fosstodonOptions.Scope.Add("read:accounts");
fosstodonOptions.Scope.Add("admin:read:accounts");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
fosstodonOptions.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
var identifier = user.Value<string>("id")?.Clean();
if (!string.IsNullOrEmpty(identifier))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.NameIdentifier, identifier,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userName = user.Value<string>("display_name")?.Clean();
if (!string.IsNullOrEmpty(userName))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.Name, userName,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userEmail = user.Value<string>("acct")?.Clean();
if (!string.IsNullOrEmpty(userEmail))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.Email, userEmail,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
}
};
});
Really wanting to know if their is an endpoint that will return current user email address. I have looked through Mastodon Api documentation but not seeing and enpoint for this.
Found that it is not posible to obtain user email through Mastodon Api. Instead decided to handel this situation in the registration process of application authentication flow. Basicly check if a user exists with given email. If so then remove the auto provision user and add external provider information to existing user. Later will submit verify email, to email address provided, to avoid someone from stealing user account through registration.
/// <summary>
/// Handle postback from new registration
/// </summary>
/// <returns>IActionResult</returns>
[AllowAnonymous]
[HttpPost("Registration/New")]
[HttpPost("Registration/New/{id?}")]
[ValidateAntiForgeryToken]
public virtual async Task<IActionResult> New([Bind(RegistrationViewModel.BindProperties)] RegistrationViewModel model, [FromForm(Name = "Button")] string button)
{
// Check if cancled
if (button.Clean() != "submit")
return RedirectToAction("Index", "Home");
// Check email is valid
if (!model.Email.Clean().IsValidEmail())
ModelState.AddModelError(nameof(model.Email), _sharedLocalizer["ErrorMessage.Invalid"]);
if (ModelState.IsValid)
{
// setup results
IdentityResult identityResult = new IdentityResult();
// Check for existing user
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email.Clean());
if (user != null)
{
if (user.Id != model.Id.Clean())
{
ApplicationUser removeUser = await _userManager.FindByIdAsync(model.Id.Clean());
if (removeUser != null)
{
identityResult = await _userManager.DeleteAsync(removeUser);
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
}
}
}
else
{
user = await _userManager.FindByIdAsync(model.Id.Clean());
if (user == null)
throw new KeyNotFoundException($"[Key]: {nameof(model.Id)} [Value]: {model.Id}");
}
user.DisplayName = model.DisplayName.Clean();
user.UserName = model.Email.Clean();
user.NormalizedUserName = model.Email.Clean();
user.Email = model.Email.Clean();
user.NormalizedEmail = model.Email.Clean();
identityResult = await _userManager.UpdateAsync(user);
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
if (!string.IsNullOrEmpty(model.ProviderUserId.Clean()))
{
var userLogins = await _userManager.GetLoginsAsync(user);
UserLoginInfo? userLogin = userLogins
.Where(x => x.LoginProvider == model.Provider.Clean())
.Where(x => x.ProviderKey == model.ProviderUserId.Clean())
.FirstOrDefault();
if (userLogin == null)
{
identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(model.Provider.Clean(), model.ProviderUserId.Clean(), model.Provider.Clean()));
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
}
}
}
return View(model);
}

How to keep user logged in when using FB plugin to sign in?

I am trying to find out how to keep user logged in. I am using Facebook Plugin and storing the user data in CosmosDB, however i am not sure what data do i need to keep the user logged in or when to actually ask his permission as i am redirected straight to Facebook login.
This is the code that i am using to sign in
async Task LoginFacebookAsync(User user)
{
try
{
if (_facebookService.IsLoggedIn)
{
_facebookService.Logout();
}
EventHandler<FBEventArgs<string>> userDataDelegate = null;
userDataDelegate = async (object sender, FBEventArgs<string> e) =>
{
if (e == null) return;
switch (e.Status)
{
case FacebookActionStatus.Completed:
var facebookProfile = await Task.Run(() => JsonConvert.DeserializeObject<FacebookProfile>(e.Data));
var socialLoginData = new User
{
UserEmail = facebookProfile.Email,
UserName = $"{facebookProfile.FirstName} {facebookProfile.LastName}",
Id = facebookProfile.UserId,
};
user.UserEmail = socialLoginData.UserEmail;
user.UserName = socialLoginData.UserName;
user.Id = socialLoginData.Id;
user = await UserViewModel.GetOrCreateUser(user);
UserViewModel.SetUser(user);
await App.Current.SavePropertiesAsync();
App.Current.MainPage = new AppShell();
break;
case FacebookActionStatus.Canceled:
break;
}
_facebookService.OnUserData -= userDataDelegate;
};
_facebookService.OnUserData += userDataDelegate;
string[] fbRequestFields = { "email", "first_name", "gender", "last_name" };
string[] fbPermisions = { "email" };
await _facebookService.RequestUserDataAsync(fbRequestFields, fbPermisions);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
Please if you could help how to approach this.
You may use local db like Sqlite or Settings Plugin to save the authentication token, expire date etc. So when the app run you can check them and let the user automatically login or not.

The procedure does not work properly Entity Framework ASP.NET MVC 5 C#5

I have been facing this problem with assigning users to a proper role. The code looks just fine, but in reality half of the users gets a proper role, the other half stays without a role at all. Here is the method which does it:
public IdentityResult RefreshUserGroupRoles(long? userId)
{
if (userId == null) throw new ArgumentNullException(nameof(userId));
var user = _userManager.FindById(userId.Value);
if(user == null)
{
throw new ArgumentNullException(nameof(userId));
}
// Remove user from previous roles:
var oldUserRoles = _userManager.GetRoles(userId.Value);
if (oldUserRoles.Count > 0)
{
_userManager.RemoveFromRoles(userId.Value, oldUserRoles.ToArray());
}
// Find the roles this user is entitled to from group membership:
var newGroupRoles = this.GetUserGroupRoles(userId.Value);
// Get the damn role names:
var allRoles = _roleManager.Roles.ToList();
var addTheseRoles = allRoles.Where(r => newGroupRoles.Any(gr => gr.AppRoleId == r.Id));
var roleNames = addTheseRoles.Select(n => n.Name).ToArray();
//_db.Database.CurrentTransaction.Commit();
// Add the user to the proper roles
var transaction = _db.Database.BeginTransaction();
IdentityResult result;
try
{
result = _userManager.AddToRoles(userId.Value, roleNames);
transaction.Commit();
_db.DbContextTransactionAu.Commit(); //This is for Audit
}
catch (Exception)
{
transaction.Rollback();
throw;
}
_db.DbContextTransactionAuDispose?.Dispose();
return result;
}
public IEnumerable<AppGroupRole> GetUserGroupRoles(long userId)
{
var userGroups = this.GetUserGroups(userId).ToList();
if (userGroups.Count == 0) return new Collection<AppGroupRole>().AsEnumerable();
var userGroupRoles = new List<AppGroupRole>();
foreach(var group in userGroups)
{
userGroupRoles.AddRange(group.AppRoles.ToArray());
}
return userGroupRoles;
}
Any idea what could be wrong?

IdentityServer3: Some Claims not being returned from identity server

Context:
I am using ASP.NET MVC with OWIN self host. Below are the rest of the configs/setup.
In my Clients in identity server (notice the AllowedScopes set):
public static class InMemoryClientSource
{
public static List<Client> GetClientList()
{
return new List<Client>()
{
new Client()
{
ClientName = "Admin website",
ClientId = "admin",
Enabled = true,
Flow = Flows.Hybrid,
ClientSecrets = new List<Secret>()
{
new Secret("admin".Sha256())
},
RedirectUris = new List<string>()
{
"https://admin.localhost.com/"
},
PostLogoutRedirectUris = new List<string>()
{
"https://admin.localhost.com/"
},
AllowedScopes = new List<string> {
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
Constants.StandardScopes.Roles
}
}
};
}
}
Here are the Scopes:
public static class InMemoryScopeSource
{
public static List<Scope> GetScopeList()
{
var scopes = new List<Scope>();
scopes.Add(StandardScopes.OpenId);
scopes.Add(StandardScopes.Profile);
scopes.Add(StandardScopes.Email);
scopes.Add(StandardScopes.Roles);
return scopes.ToList();
}
}
In the Identity Server, here's how the server is configured. (Notice the Clients and Scopes are the ones provided above) :
var userService = new UsersService( .... repository passed here .... );
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(InMemoryClientSource.GetClientList())
.UseInMemoryScopes(InMemoryScopeSource.GetScopeList());
factory.UserService = new Registration<IUserService>(resolver => userService);
var options = new IdentityServerOptions()
{
Factory = factory,
SigningCertificate = Certificates.Load(), // certificates blah blah
SiteName = "Identity"
};
app.UseIdentityServer(options);
Finally, on the client web application side, this is how auth is set up:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Authority = "https://id.localhost.com",
ClientId = "admin",
RedirectUri = "https://admin.localhost.com/",
PostLogoutRedirectUri = "https://admin.localhost.com/",
ResponseType = "code id_token token",
Scope = "openid profile email roles",
ClientSecret = "admin",
SignInAsAuthenticationType = "Cookies"
});
I have implemented a custom class for IUserService:
public class UsersService : UserServiceBase
{
public UsersService( .... repository passed here .... )
{
//.... ctor stuff
}
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// var user = .... retrieved from database .....
// ... auth logic ...
if (isAuthenticated)
{
var claims = new List<Claim>();
claims.Add(new Claim(Constants.ClaimTypes.GivenName, user.FirstName));
claims.Add(new Claim(Constants.ClaimTypes.FamilyName, user.LastName));
claims.Add(new Claim(Constants.ClaimTypes.Email, user.EmailAddress));
context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.EmailAddress, claims);
}
return Task.FromResult(0);
}
}
As you see, the claims are passed in this line:
context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.EmailAddress, claims);
When I try logging in to IdentityServer3, I can log in successfully to the client web application. HOWEVER, when I get the user claims, I don't see any identity claims. No given_name, family_name, and email claims. Screenshot below:
Anything I might have missed? Thanks in advance!
My solution was to add a list of claims to my scope configuration in order to return those claims. The wiki's documentation here described it.
For an in-memory client all I did was something like this:
public class Scopes
{
public static IEnumerable<Scope> Get()
{
return new Scope[]
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.Roles,
StandardScopes.OfflineAccess,
new Scope
{
Name = "yourScopeNameHere",
DisplayName = "A Nice Display Name",
Type = ScopeType.Identity,
Emphasize = false,
Claims = new List<ScopeClaim>
{
new ScopeClaim("yourClaimNameHere", true),
new ScopeClaim("anotherClaimNameHere", true)
}
}
};
}
}
Finally found the solution for this problem.
First, I moved the creation of claims to the overridden GetProfileDataAsync (in my UserService class). Here's my implementation of it:
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var identity = new ClaimsIdentity();
UserInfo user = null;
if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
user = _facade.Get(context.Subject.Identity.Name);
else
{
// get the sub claim
var claim = context.Subject.FindFirst(item => item.Type == "sub");
if (claim != null)
{
Guid userId = new Guid(claim.Value);
user = _facade.Get(userId);
}
}
if (user != null)
{
identity.AddClaims(new[]
{
new Claim(Constants.ClaimTypes.PreferredUserName, user.Username),
new Claim(Constants.ClaimTypes.Email, user.EmailAddress)
// .. other claims
});
}
context.IssuedClaims = identity.Claims; //<- MAKE SURE you add the claims here
return Task.FromResult(identity.Claims);
}
Make sure that we pass the claims to the "context.IssueClaims" inside the GetProfileDataAsync() before returning the task.
And for those interested on how my AuthenticateLocalAsync() looks like:
var user = _facade.Get(context.UserName);
if (user == null)
return Task.FromResult(0);
var isPasswordCorrect = BCrypt.Net.BCrypt.Verify(context.Password, user.Password);
if (isPasswordCorrect)
{
context.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.Username);
}
return Task.FromResult(0);
I raised a similar issue in IdentityServer3 GitHub project page that contains the explanation on why I encountered my issue. Here's the link:
https://github.com/IdentityServer/IdentityServer3/issues/1938
I am not using the identity server, however I am using the Windows Identity Foundation, which I believe is what IdentityServer uses. In order to access the claims I use:
((ClaimsIdentity)User.Identity).Claims

Pass a variable from a Custom Filter to controller action method

I have a Web Api project.
I have implemented a custom Authentication Attribute like so:
public class TokenAuthenticationAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// In auth web method you should implement functionality of authentication
// so that client app could be able to get token
if (actionContext.Request.RequestUri.AbsolutePath.Contains("api/auth/login"))
{
return;
}
// Receive token from the client. Here is the example when token is in header:
var token = HttpContext.Current.Request.Headers["Token"];
// Put your secret key into the configuration
var secretKey = ConfigurationManager.AppSettings["JWTSecurityKey"];
try
{
string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey);
int separatorIndex = jsonPayload.IndexOf(';');
string userId = "";
DateTime timeIssued = DateTime.MinValue;
if (separatorIndex >= 0)
{
//userId = UTF8Encoding.UTF8.GetString(Convert.FromBase64String(jsonPayload.Substring(0, separatorIndex)));
userId = jsonPayload.Substring(0, separatorIndex);
timeIssued = DateTime.Parse(jsonPayload.Substring(separatorIndex + 1));
}
short TokenTTL = 10;
//try{
//Int16.TryParse(ConfigurationManager.AppSettings["TokenTTL"],TokenTTL);
//}catch(Exception e){ //}
if ((DateTime.Now.Subtract(timeIssued).TotalMinutes >= TokenTTL))
{
throw new HttpResponseException(HttpStatusCode.Forbidden);
}
//Save user in context
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, userId)
};
var id = new ClaimsIdentity(claims, "Basic");
var principal = new ClaimsPrincipal(new[] { id });
actionContext.Request.GetRequestContext().Principal = principal;
}
catch (JWT.SignatureVerificationException)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
}
Now how do I get hold of that user in my actionmethod?
[BasicHttpAuthorizeAttribute]
[httpGet]
public void Login()
{
// how do i get user here
}
/////// Save the string username to the context so that I can acess
it in the controler.
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, "john")
};
var id = new ClaimsIdentity(claims, "Basic");
var principal = new ClaimsPrincipal(new[] { id });
actionContext.Request.GetRequestContext().Principal = principal;
// how do i get user here
var name = User.Identity.Name;
BTW, use an authentication filter instead of an authorization filter to perform authentication. See my blog post - http://lbadri.wordpress.com/2014/02/13/basic-authentication-with-asp-net-web-api-using-authentication-filter/.

Resources