I'm using the spring-security-facebook plugin for authentication. It works well, now I'm trying to use some functions of spring-social-facebook that needs authorization. From my controller, where can I get a valid accessToken (in order to create a FacebookTemplate object) ?
This is how I'm using the plugin:
1) I added a domain class, OAuthUser (not in the plugin but in my project)
2) I have generated a FacebookAuthDaoImpl
3) I edited the methods generated, for example in create(), I create
+ an instance of a user (main domain class SecUser) and set the profile infos
+ a new OAuthUser (where I set the uid, the accessToken, and relate it to the main user created.
UPDATE 1:
I added those 3 methods in my FacebookAuthDaoImpl class:
Boolean hasValidToken(OAuthUser user){
def now= new Date()
if(now.after(user.accessTokenExpires)){
return false
} else {
return true
}
}
void updateToken(OAuthUser user, FacebookAuthToken token){
user.accessToken = token.accessToken
user.save()
}
String getAccessToken(OAuthUser user){
return user.accessToken
}
But I still have the expired AccessToken.
If you're using default DAO:
It's stored at field accessToken of your domain object for Facebok User.
If you don't have field named accessToken, you should add it (String accessToken). Ideally with an additional field: Date accessTokenExpires. And both fields will be automatically filled by the plugin.
If you've created your own DAO implementation, then:
create(FacebookAuthToken token) pass access token as token.accessToken.accessToken. You can store it anywhere you wish
Boolean hasValidToken(F user), void updateToken(F user, FacebookAuthToken token) and getAccessToken(F user) - first should check token expiration, second update with new value (called when token is expired) and last should retourn current value.
As you said, you have you own DAO implentation. How you're implemented last 3 methods?
Related
How we can pass extra params (which we send as query params to endsession endpoint) to Logout when user is not authenticated in IDP SSO
I'm using latest Identity Server 4.
In the standard case, when client initiates a logout (by accessing endsession endpoint), everything works fine when we have information about the user (which is stored in a cookie and endsession endpoint can successfully read that). EndSession redirects to https://myidp/Account/Logout?logoutId=someId and we can get any parameter which was passed in query string to endsession endpoint
But when we try to do second logout from the client (and there is no authenticated user in cookie), logoutId parameter is not passed to Logout endpoint and there is no chance to get params which we sent in query string to endsession endpoint
The reason why we need this is simple:
suppose client clicked logout twice on 2 different pages (client's pages)
when user was logged out, we want to redirect it back to client URL OR to add some extra logic depending on params which we send to endsession endpoint. But as we don't get any params in Logout method - we don't know anything about the client and what to do with this logout request
For now as workaround of this problem I added middleware in .Net Core app, which validates LogoutId parameter. If it doesn't exist (the case when we will not be able to get initial parameters, as no logoutId was passed in the query string to logout method), I add manually query string parameters from my middleware to the redirected URL
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace IdentityProviderWebApp.Core
{
public class EndRequestMiddleware
{
private readonly RequestDelegate _next;
public EndRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
if (context.Request.Path.Value == "/connect/endsession")
{
var logoutUrl = context.Response.Headers["location"].ToString();
if (!logoutUrl.Contains("?"))
{
var fixedLocation = context.Response.Headers["location"] + context.Request.QueryString;
context.Response.Headers.Remove("location");
context.Response.Headers.Add("location", new StringValues(fixedLocation));
}
}
}
}
}
Register middleware code
app.UseMiddleware<EndRequestMiddleware>();
In AccountController Logout method get your expected variables as parameters of logout method
public async Task<IActionResult> Logout(string logoutId, string client_id, string redirect_uri)
In the Logout method get actual variable either from a valid context (if LogoutId exists) or use value which you receive in your Logout method
var logout = await _interaction.GetLogoutContextAsync(logoutId);
clientId = logout.Parameters.Get("client_id") ?? clientId;
redirectUri = logout.Parameters.Get("redirect_uri") ?? redirectUri;
Hope someone will find better approach
I've created a Blazor Server App with the option to scaffold an identity system. This created an Entity Framework IdentityDbContext with a number of tables to manage user logins and settings. I decided to keep my own DbContext separate from this so that I could replace either of the contexts later, if necessary.
What I would like to do is have a User entity in my own custom dbcontext, and in it store a reference to the user id of the scaffolded IdentityDbContext entity. I would also like to ensure that I don't have to query the db for the custom entity every time the user opens a new page.
I've been looking around StackOverflow trying to find good suggestions of how to approach this, but I'm still not sure how to start. So I have a few questions:
Is my approach a sensible one?
How do I find a permanent id number or string to couple with on the UserIdentity?
Should I store my custom user entity in some sort of context so I don't have to query it all the time? If so, how?
All help is greatly appreciated!
It looks like your requirement is to store custom information about the current user above and beyond what is stored in Identity about the current user.
For simpler use cases you can create your own User class derived from IdentityUser and add additional properties on there and let Identity take care of all persistence and retrieval.
For more complex use cases you may follow the approach you have taken, whereby you create your own tables to store user related information.
It seems that you have taken the second approach.
Is my approach a sensible one?
I think so. Burying lots of business-specific context about the user in the Identity tables would tightly bind you to the Identity implementation.
How do I find a permanent id number or string to couple with on the
UserIdentity?
IdentityUser user = await UserManager<IdentityUser>.FindByNameAsync(username);
string uniqueId = user.Id;
// or, if the user is signed in ...
string uniqueId = UserManager<IdentityUser>.GetUserId(HttpContext.User);
Should I store my custom user entity in some sort of context so I
don't have to query it all the time? If so, how?
Let's say you have a class structure from your own DbContext that stores custom information about the user, then you can retrieve that when the user signs in, serialize it, and put it in a claim on the ClaimsPrincipal. This will then be available to you with every request without going back to the database. You can deserialize it from the Claims collection as needed and use it as required.
How to ...
Create a CustomUserClaimsPrincipalFactory (this will add custom claims when the user is authenticated by retrieving data from ICustomUserInfoService and storing in claims):
public class CustomUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
private readonly ICustomUserInfoService _customUserInfoService;
public CustomUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ICustomUserInfoService customUserInfoService)
: base(userManager, roleManager, optionsAccessor)
{
_customUserInfoService= customUserInfoService;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(
ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
MyCustomUserInfo customUserInfo =
await _customUserInfoService.GetInfoAsync();
// NOTE:
// ... to add more claims, the claim type need to be registered
// ... in StartUp.cs : ConfigureServices
// e.g
//services.AddIdentityServer()
// .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
// {
// options.IdentityResources["openid"].UserClaims.Add("role");
// options.ApiResources.Single().UserClaims.Add("role");
// options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
// options.ApiResources.Single().UserClaims.Add("my-custom-info");
// });
List<Claim> claims = new List<Claim>
{
// Add serialized custom user info to claims
new Claim("my-custom-info", JsonSerializer.Serialize(customUserInfo))
};
identity.AddClaims(claims.ToArray());
return identity;
}
}
Register your CustomUserInfoService in Startup.cs (your own service to get your custom user info from the database):
services.AddScoped<ICustomUserInfoService>(_ => new CustomUserInfoService());
Register Identity Options (with your CustomUserClaimsPrincipalFactory and authorisation in Startup.cs. NOTE: addition of "my-custom-info" as a registered userclaim type. Without this your code in CustomUserInfoService will fail to add the claim type "my-custom-info":
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
options.ApiResources.Single().UserClaims.Add("my-custom-info");
});
You can then retrieve your custom user info from claims, without returning to database, by using:
MyCustomUserInfo customUserInfo =
JsonSerializer.Deserialize<MyCustomUserInfo>(
HttpContext.User.Claims
.SingleOrDefault(c => c.Type == "my-custom-info").Value);
when sub classing shiro's AuthorizingRealm (or only AuthenticationRealm) by overriding
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
}
Is it my job to check that the credentials provided in the AuthenticationToken actually match?
Or am I supposed to return the AuthenticationInfo with the principals resolved from the AuthenticationToken and the correct password for the given credentials and shiro will compare them on its own somewhere within the flow of the Subject.login(AuthenticationToken) call?
The Javadocs for AuthenticatingRealm.doGetAuthenticationInfo() state (emphasis mine):
Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given authentication token.
For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific log-in logic in addition to just retrieving data - it is up to the Realm implementation.
The method AuthenticatingRealm.getAuthenticationInfo() first calls doGetAuthenticationInfo() then subsequently calls assertCredentialsMatch() using the configured credentialsMatcher:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
So depending on how typical your Realm implementation is, you might want to avoid checking the AuthenticationToken's credentials in the doGetAuthenticationInfo() method, because the getAuthenticationInfo() template method already contains a step to ensure the submitted credentials match.
To specifically address your question if it is your responsibility "to check that the credentials provided in the AuthenticationToken actually match", the answer is yes, but not in the doGetAuthenticationInfo() method. Typically you would perform the credentials comparison within an implementation of the CredentialsMatcher interface, as described here.
Inside doGetAuthenticationInfo(...) you need to verify that the user has provided you with authentication proof.
Here is a pseudo-coded example of what you might do:
protected AuthenticationInfo doGetAutheticationInfo(AuthenticationToken token) {
if(token instanceof UsernamePasswordToken) {
String username = token.getUsername();
// Look up the user by the provide username
UserRecord userRecord = lookupUserRecord(username);
// No record found - don't know who this is
if (userRecord == null) {
throw new UnknownAccountException();
}
// Check for other things, like a locked account, expired password, etc.
// Verify the user
SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(userRecord.getPrincipal(), userRecord.getHashedCredentials(), userRecord.getCredentialsSalt(), getName());
boolean successfulAuthentication = getCredentialsMatcher().doCredentialsMatch(token, sai);
if(successfulAuthentication) {
// Check for anything else that might prevent login (expired password, locked account, etc
if (other problems) {
throw new CredentialsException(); // Or something more specific
}
// Success!
return sai;
} else {
// Bad password
throw new IncorrectCredentialsException();
}
}
// Don't know what to do with this token
throw new CredentialsException();
}
You'll have to write lookupUserRecord(username) or something similar to go lookup the user information including his hashed and salted credentials.
doGetAuthenticationInfo is the main method where authentication is done. SO if you override it generally you are overriding authentication process. If you want to use the process that was defined for that reealm and do some extra things better call super class method first then get its info and then use it so you will not have to change anything. Also in case of jdbcrealm sqls in shiro.ini are automatically mapped. and they will not be changed until you override
setAuthenticationQuery, setUserRolesQuery, etc
You can easily call following method to simulate the actual process then customize it.
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
Note, that super is a reference to the parent, but super() is it's constructor.
like:
public class CustomJdbcRealm extends JdbcRealm
{
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
AuthenticationInfo info = super.doGetAuthenticationInfo(token);
// Your own code here
}
}
How to authenticate and redirect a user to his own page i.e to www.mysite.com/"user's email".
I am using the following algo which is not working...
userDB in User class:
Map<String,String> userdata=new HashMap<String,String>();
First my login process form :
#Path("/login")
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void login(
#FormParam("email") String emailc,
#FormParam("password") String pass,
#Context HttpServletResponse servletResponse
) throws IOException,RuntimeException {
User u1=new User();
pass=u1.getPassword();
emailc=u1.getEmailaddrs();
boolean checked=false;
boolean exists;
exists=u1.userdata.containsKey(emailc);
if(exists){
String mypass =u1.userdata.get(emailc);
if(mypass==pass){
checked=true;
}else{
checked=false;
}
}else{
checked=false;
}
if(!checked){
//User Doesn't exists
servletResponse.sendRedirect("http://localhost:8080/MySite/pages/Create_Profile.html");
}else{
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
}
}
createprofile
#POST
#Produces(MediaType.TEXT_HTML)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void newUser(
#FormParam("email") String email,
#FormParam("password") String password,
#Context HttpServletResponse servletResponse
) throws IOException {
User u = new User(email,password);
User.userdata.put(email,password);
}
Your usage of userdata [Map] looks wrong to me. Is it a part of user class, is it non static or static ?
If it is non static then every time you will do new User() .. that map will be initialized and it will have no data in it. Hence u1.userdata.containsKey(emailc); will be always false.
If you are using a hashmap as a temporary database for dev purposes then, make it static rather keep it in a different class like UserStore or some DB access layer. Exmaple below:
public class UserDAO(){
private static Map<String,User> userdata = new HashMap<String,User>();
public boolean hasUser(String email){
return userdata.contains(email);
}
public User saveUser(String email, String password ...){
//make user object save it in map and return the same
}
// more methods for delete and edit etc.
}
And use this in your REST layer classes like this
exists = userDao.hasUser(email);
Advantages :
Your problem will be solved.
Later on when you move to actual db implementation you will just have to change your UserDao code and rest application code will be just fine. -- Loose coupling :)
Also regarding forward using email
servletResponse.sendRedirect("http://localhost:8080/MySite/{email}"); <<<< How to redirect using #FormParam("email")
add the email parameter there in the url only, if thats what you want:
servletResponse.sendRedirect("http://localhost:8080/MySite/"+emailc);
UPDATE :
See the fundamental thing is that you get request parameters [email , password]. You check it whether it is present in map or not. Now what you are doing wrong here is you create a new user like this User u = new User(); and then get email and password from it emailc = u.getEmail();. This emailc will always be null and your userdata map will always return false for that. You have two choices :
Either set email and password in user object and then get the data from user object.
Use the email and password obtained from request parameters for your logic. Do not alter them
One good practice to follow while programming is that at all times think of your method parameters as final parameters.
UPDATE 2 :
if(mypass==pass){
checked=true;
}else{
checked=false;
}
Change == to equals method. String matching should be done by equals or equalsIgnoreCase method not ==.
You always create a new User without any parameters: User u1=new User();. All these User instances will have the same property values and probably exists is always false.
I have REST service requirements in which some calls require authentication and some don't. Absolutely no state is used, as the calls are all independent from one another. I have put something together which seems to work, but is this the right way to go about not using sessions?
This question is kind of related to my WCF question which is answered here.
Firstly I registered the authentication method:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}
));
I then attribute the respective calls (or service or DTO) with the Authenticate attribute:
[Authenticate]
public HelloResponse Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name + " with POST & Auth"};
}
I inherit from the BasicAuthProvider class which does the authentication:
public class CustomCredentialsAuthProvider : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
return userName == "dylan" && password == "abc123";
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
session.IsAuthenticated = true;
//Important: You need to save the session!
authService.SaveSession(session, new TimeSpan(0,0,10));
}
}
As you can see, I do save the session but it times out after 10 seconds. This is the part that I'm sure can potentially be done better. It seems to work nicely though.
Is there a better way of doing what I'm trying to accomplish?
Is there also any way, due to the sessionless nature of these services, to remove the Auth, AssignRoles and UnassignRoles methods?
If you wanted to keep using ServiceStack's Authentication and Session support you could just add a response filter that clears the user session after the service is executed, e.g:
this.ResponseFilters.Add((req, res, dto) => req.RemoveSession());
Basically after each request is executed it clears the session, so no record of them having authenticated exists.
Otherwise you can just skip using ServiceStack's Authentication completely and just provide your own via RequestFitlers of FilterAttributes (which is essentially what SS Auth does under the hood).