This is a very similar question to this
aspnet identity invalid token on confirmation email
but the solutions are not valid because I am using the new ASP.NET Core 1.0 that includes ASP.NET Core Identity.
My scenario is as follows:
In the back end (ASP.NET Core) I have a function that sends a password reset email with a link. In order to generate that link I have to generate a code using Identity. Something like this.
public async Task SendPasswordResetEmailAsync(string email)
{
//_userManager is an instance of UserManager<User>
var userEntity = await _userManager.FindByNameAsync(email);
var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
var link = Url.Action("MyAction", "MyController", new { email = email, code = tokenGenerated }, protocol: HttpContext.Request.Scheme);
//this is my service that sends an email to the user containing the generated password reset link
await _emailService.SendPasswordResetEmailAsync(userEntity , link);
}
this would generate an email with a link to:
http://myapp:8080/passwordreset?code=CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX+5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu+jHX5cajptUBiVqIChiwoTODh7ei4+MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5+jebokKkR5HEJU+mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
Then my AngularJs application would present a view with a form to enter and confirm the new password, and would PUT a JSON object with the new password and the code that got from the query parameter in the URL.
Finally my back end would get the PUT request, grab the code and validate it using Identity like this:
[HttpPut]
[AllowAnonymous]
[Route("api/password/{email}")]
public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
{
//some irrelevant validatoins here
await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
return Ok();
}
The problem is that Identity responds with an
Invalid token
error. I have found that the problem is that the codes don't match and the above code would be received back in the JSON object in the PUT request as follows:
CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX 5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu jHX5cajptUBiVqIChiwoTODh7ei4 MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5 jebokKkR5HEJU mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
Notice that where there was + symbols now there are spaces symbols and obviously that causes Identity to think the tokens are different.
For some reason Angular is decoding the URL query parameter in a different way that was encoded.
How to resolve this?
This answer https://stackoverflow.com/a/31297879/2948212 pointed me in the right direction. But as I said it was for a different version and now it is slightly different solution.
The answer is still the same: encode the token in base 64 url, and then decode it in base 64 url. That way both Angular and ASP.NET Core will retrieve the very same code.
I needed to install another dependency to Microsoft.AspNetCore.WebUtilities;
Now the code would be something like this:
public async Task SendPasswordResetEmailAsync(string email)
{
//_userManager is an instance of UserManager<User>
var userEntity = await _userManager.FindByNameAsync(email);
var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
byte[] tokenGeneratedBytes = Encoding.UTF8.GetBytes(tokenGenerated);
var codeEncoded = WebEncoders.Base64UrlEncode(tokenGeneratedBytes);
var link = Url.Action("MyAction", "MyController", new { email = email, code = codeEncoded }, protocol: HttpContext.Request.Scheme);
//this is my service that sends an email to the user containing the generated password reset link
await _emailService.SendPasswordResetEmailAsync(userEntity , link);
}
and when receiving back the code during the PUT request
[HttpPut]
[AllowAnonymous]
[Route("api/password/{email}")]
public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
{
//some irrelevant validatoins here
await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
return Ok();
}
//in MyIdentityWrapperService
public async Task ResetPasswordAsync(string email, string password, string code)
{
var userEntity = await _userManager.FindByNameAsync(email);
var codeDecodedBytes = WebEncoders.Base64UrlDecode(code);
var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
await _userManager.ResetPasswordAsync(userEntity, codeDecoded, password);
}
I had a similar issue and I was encoding my token but it kept on failing validation and the problem turned out to be this : options.LowercaseQueryStrings = true;
Do not set true on options.LowercaseQueryStrings this alters the validation token's integrity and you will get Invalid Token Error.
// This allows routes to be in lowercase
services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = false;
});
I have tried the answers above, but this guide has helped me. Basically, you would need to encode the code, otherwise, you would encounter some weird bugs. To summarise you would need to do this:
string code = HttpUtility.UrlEncode(UserManager.GenerateEmailConfirmationToken(userID));
After this, if it is applicable to you, decode code:
string decoded = HttpUtility.UrlDecode(code)
After scaffolding the ConfirmEmail page in my Asp.Net Core 3.0 project I ran into the same problem.
Removing the following line from the OnGetAsync method in ConfirmEmail.cshtml.cs fixed the problem:
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
In the scaffolded Login page, the code is added to the callbackUrl which is then URL encoded using HtmlEncoder.Default.Encode(callbackUrl). When the link is clicked the decoding is automatically done and the code is like it should be to confirm the email.
UPDATE:
I noticed that during the Forgot Password process the code is Base64 encoded before being put in the callbackUrl which then means that the Base64 decode IS necessary.
A better solution would thus be to add the following line to wherever the code is generated before adding it to the callbackUrl.
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
Here is a link to the issue which has been fixed.
(according this post: https://stackoverflow.com/a/27943434/9869427)
For the resetPasswordAsync (identity manager) "token invalid" problem... because "+" become space in url...
use Uri.EscapeUriString
exemple: in my sendResetPasswordByMailAsync
var token = "Aa+Bb Cc";
var encodedToken = Uri.EscapeDataString(token);
encodedToken = "Aa%20Bb2B%Cc"
var url = $"http://localhost:4200/account/reset-password?email={email}&token={encodedToken}";
var mailContent= $"Please reset your password by <a href='{url}'>clicking here</a>.";
Now u can click on your link, and you will go to the good url with "+" (encode by %2B)... your token won't be invalid...
I had the same issue while hosting my website using Cloud Run in GCP, and none of the solutions here worked for me.
In my case the problem was with the data protection keys being stored locally in the instance. The following entry in the logs hinted the problem:
Storing keys in a directory '/home/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
Because of this, the tokens were only valid to the instance that issued them, which was already destroyed by the time the users clicked the link sent to their email.
The solution was to use a distributed storage for the keys, for instance, a database via EntityFramework:
using Microsoft.AspNetCore.DataProtection;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToDbContext<DbContext>();
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<AnotherDbContext>()
.AddDefaultTokenProviders();
}
The details of the different types of storages can be found here.
Had a similar issue for ASP Core 2.1, and was scratching my head, because any encoding/decoding for code(token) did not work for me. And I always had an Invalid token error for userManager.ConfirmEmailAsync(user, code)
SOLUTION:
Turned out that the issue was that the user was created not with UserManager but using dbcontext like _dbContext.Users.AddAsync after replacing this creation method with _userManager.CreateAsync everything worked fine for me even without any encoding/decoding for code(token).
In my case, this problem was due to OnPostAsync method in RegisterModel encoding the callback url:
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
This encoding (the application of HtmlEncoder.Default.Encode() to callbackUrl), made the url '&' become '&', thus invalidating the whole link.
You can also use regex when verifying the token in the reset put.
var decode = token.Replace(" ", "+");
await _userManager.ResetPasswordAsync(user, decode, Password);
Related
I'm attempting to call Identity Server 4 with Flurl
Im getting a 400 back from the ID server.
The credentials etc work when I make the call with PostMan or with the methods available in IdentityModel.Client.
I am unsure about the Post i.e. PostUrlEncodedAsync
As you can see I have tried various combinations.
This.. does not work
var address = "http://localhost:8027/connect/token";
var x = await address
.WithBasicAuth("clientName", "secretValue")
.SetQueryParams(
new
{
GrantType = "client_credentials",
Scope = "requiredScope"
}
)
.PostUrlEncodedAsync(new { });
//.SendAsync(HttpMethod.Post, null, CancellationToken.None);
//.SendAsync(HttpMethod.Post, y);
This.. does.
var apiClientCredentials = new ClientCredentialsTokenRequest()
{
Address = "http://localhost:8027/connect/token",
ClientId = "clientName",
ClientSecret = "secretValue",
Scope = "requiredScope"
};
var client = new HttpClient();
var tokenResponse = await client.RequestClientCredentialsTokenAsync(apiClientCredentials);
if (tokenResponse.IsError)
{
}
I can see that deep doen inside 'RequestClientCredentialsTokenAsync' it does parameters.Add to add grant type and scope hence I have added those as params.
Have also tried adding the client id and secret to the query params.
Same 400 message.
Any help greatly appreciated.
I believe the difference is that IdentityModel knows to serialize those ClientCredentialsTokenRequest property names like ClientId and GrantType to snake-case, i.e. client_id and grant_type, as required by OAuth2. When you hand Flurl an anonymous object, it just serializes the property names exactly as-is. To make it work with Flurl you can either create a class and decorate it with Json.NET serialization attributes, or (what I typically do) keep it simple take some liberties with C# property name conventions:
.SetQueryParams(
new
{
grant_type = "client_credentials",
scope = "requiredScope"
}
How can I post message as a bot(async) in new hangouts chat without using the Google App Engine. I have gone through the examples, but all of them use App Engine for authentication, but i need to authenticate it without using the same.
Here is a code sample that connects to a chat using an http request and a webhook from Google Hangout Chat with a Python script. Webhooks are the only alternative to using a service account. More info here: https://developers.google.com/hangouts/chat/how-tos/webhooks
`from httplib2 import Http
from json import dumps
#
# Hangouts Chat incoming webhook quickstart
#
def main():
url = '<webhook url here>'
bot_message = {
'text' : 'text go here'}
message_headers = { 'Content-Type': 'application/json; charset=UTF-8'}
http_obj = Http()
response = http_obj.request(
uri=url,
method='POST',
headers=message_headers,
body=dumps(bot_message),
)
print(response)
if __name__ == '__main__':
main()
`
If your bot implementation is with google app script try to do it with google service account and as indicated here an example of async message
// Example bot for Hangouts Chat that demonstrates bot-initiated messages
// by spamming the user every minute.
//
// This bot makes use of the Apps Script OAuth2 library at:
// https://github.com/googlesamples/apps-script-oauth2
//
// Follow the instructions there to add the library to your script.
// When added to a space, we store the space's ID in ScriptProperties.
function onAddToSpace(e) {
PropertiesService.getScriptProperties()
.setProperty(e.space.name, '');
return {
'text': 'Hi! I\'ll post a message here every minute. ' +
'Please remove me after testing or I\'ll keep spamming you!'
};
}
// When removed from a space, we remove the space's ID from ScriptProperties.
function onRemoveFromSpace(e) {
PropertiesService.getScriptProperties()
.deleteProperty(e.space.name);
}
// Add a trigger that invokes this function every minute via the
// "Edit > Current Project's Triggers" menu. When it runs, it will
// post in each space the bot was added to.
function onTrigger() {
var spaceIds = PropertiesService.getScriptProperties()
.getKeys();
var message = { 'text': 'Hi! It\'s now ' + (new Date()) };
for (var i = 0; i < spaceIds.length; ++i) {
postMessage(spaceIds[i], message);
}
}
var SCOPE = 'https://www.googleapis.com/auth/chat.bot';
// The values below are copied from the JSON file downloaded upon
// service account creation.
var SERVICE_ACCOUNT_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n';
var SERVICE_ACCOUNT_EMAIL = 'service-account#project-id.iam.gserviceaccount.com';
// Posts a message into the given space ID via the API, using
// service account authentication.
function postMessage(spaceId, message) {
var service = OAuth2.createService('chat')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setClientId(SERVICE_ACCOUNT_EMAIL)
.setPropertyStore(PropertiesService.getUserProperties())
.setScope(SCOPE);
if (!service.hasAccess()) {
Logger.log('Authentication error: %s', service.getLastError());
return;
}
var url = 'https://chat.googleapis.com/v1/' + spaceId + '/messages';
UrlFetchApp.fetch(url, {
method: 'post',
headers: { 'Authorization': 'Bearer ' + service.getAccessToken() },
contentType: 'application/json',
payload: JSON.stringify(message),
});
}
You need to perform some below steps.
Create a service-account in console.developers.google.com and download the private key in JSON format
Use below modules if you code in python.
from oauth2client.service_account import ServiceAccountCredentials
from googleapiclient.discovery import build, build_from_document
from httplib2 import Http
Below snippet will post the message to user via chat.google.
scopes = ['https://www.googleapis.com/auth/chat.bot']
credentials = ServiceAccountCredentials.from_json_keyfile_name('/path/to/json',
scopes)
http = Http()
credentials.authorize(http)
chat = build('chat', 'v1', http=http)
resp = chat.spaces().messages().create(
parent=space,
body={'text': 'HELLO WORLD'}).execute()
You would require a space name where you can post the code. You will get the same from hangout chat response.
It’s possible to do so using JavaScript, python, (possibly more). You can check out examples here: https://github.com/gsuitedevs/hangouts-chat-samples/tree/master/node/basic-cloud-functions-bot
If you’re using cards and JavaScript I would encourage you to checkout my library here: https://github.com/BaReinhard/hangouts-card-helper
I’m also in the process of creating another example for JavaScript that is more async focused that should provide and example that’s a bit easier to reason about the code. Will link when the PR is pushed.
Edit:
I realize that you mentioned REST api. The above answer is more useful for a specific bot that can be accessed #mentions. However, if you can provide us with a bit more information I can better fix my answer to answer your question.
I have an angular js application, and when trying to issue the following post request :
$resource('api/'+API_VERSION+'/projects/:projectId/users/:userId')
.save(
{
projectId:$scope.project.id,
userId:id
},
{}
,function(){
// Handle callback
});
I get a 400 bad request error.
the request is handled by a spring RestController and the method looks like the following :
#RequestMapping(value = "/projects/{projectId}/users/{userID}",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#RolesAllowed(AuthoritiesConstants.USER)
void addUsers(#PathVariable Long projectId, #PathVariable Long userId) {
log.debug("REST request to add admin to project");
projectService.addUser(projectId, userId);
}
I Checked the request that is been sent to the server, and nothing bad strikes me.
The url is correct (all parameter are of valid type), and the content type is set to Application json.
Thanks in advance for any help.
Your API consumes JSON and returns void, so I think you should have consumes = MediaType.APPLICAT_JSON_VALUE in your #RequestMapping.
[Edit]:
Apart from the consumes annotation everything is fine with your back-end. Can you try making your post request with the following code :
$resource('api/'+API_VERSION+'/projects/:projectId/users/:userId',
{projectId: $scope.project.id, userId: id}).$save();
or again, creating an instance of the resource :
var Project = $resource('api/'+API_VERSION+'/projects/:projectId/users/:userId',
{projectId: $scope.project.id, userId: id});
var newProject = new Project();
newProject.$save();
And let me know if it worked ?
I am trying to do HTTP post to a google form, from a C program in my device. For a legacy form, the active form submission URL looks like below. I used these text to do a URL encoded HTTP/1.1 POST, which was successful.
https://spreadsheets.google.com/formResponse?formkey=FORMKEY&ifq&entry.0.single=ENTRY1&entry.2.single=ENTRY2&submit=Submit
For the new google form (whichever you create from google drive now), below is the active submit URL. When I use this for HTTP post, I get the Bad Request with error Code 400.
https://docs.google.com/forms/d/FORMKEY/formResponse?entry.1252261890=ENTRY1&entry.1890412746=ENTRY2
What has changed between old and new google form? I see similar problem faced by somebody elsewhere but no solution so far. Thanks for your help.
This is a javascript (google apps script) POST that is working on a current form (with one field!) Pperhaps you can get what you need from this:
function sendHttpPost() {
var fish = "I am a mackerel";
var payload =
{
"entry.2071121932" : fish
};
// Because payload is a JavaScript object, it will be interpreted as
// an HTML form. (We do not need to specify contentType; it will
// automatically default to either 'application/x-www-form-urlencoded'
// or 'multipart/form-data')
var options =
{
"method" : "POST",
"payload" : payload,
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch("https://docs.google.com/forms/d/this is the form ... key/formResponse", options);
Logger.log(response.getContentText())
}
I have the following routes:
/projects/{projectName}
and
/projects/{projectName}/Wall/{wallName}
Now I'd like to have that all GETs be allowed but PUT, POST, DELETE should only be allowed by project members i.e. users members of that project. I have a special class that given a user id and project name I can get the status of the user's membership - something like MyEnroler.getRole(userId, projectName) - where the userId is part of the request header and the projectName is taken from the URI.
I've tried a number of things but doesn't work. Here's the idea:
public class RoleMethodAuthorizer extends Authorizer {
#Override
protected boolean authorize(Request req, Response resp) {
//If it's a get request then no need for further authorization.
if(req.getMethod().equals(Method.GET))
return true;
else
{
String authorEmail = req.getClientInfo().getUser().getIdentifier();
String projectName = req.getAttributes().get("project").toString();
Role userRole = MyEnroler.getRole(authorEmail, projectName);
//forbid updates to resources if done by non-members of project
if(userRole.equals(MyEnroler.NON_MEMBER))
return false;
//for everybody else, return true
return true;
}
}
}
Now simply doing the following completely fails when creating inbound root in the Application:
Router projectRouter = new Router(getContext());
RoleMethodAuthorizer rma = new RoleMethodAuthorizer();
//Guard declaration here. Then setNext Restlet
guard.setNext(projectRouter);
projectRouter.attach("/projects/{project}",rma);
Router wallRouter = new Router(getContext());
wallRouter.attach("/Wall/{wallName}", WallResource.class);
rma.setNext(wallRouter);
//return guard;
So a request to /projects/stackoverflow/Wall/restlet fails. The URL is never found. I'm guessing since it's trying to match it with the projectRouter. Well I tried the various modes (MODE_BEST_MATCH or MODE_FIRST/NEXT_MATCH) to no avail.
Nothing seems to work. Conceptually this should work. I'm only intercepting a call and just being transparent to the request, but don't know how things are working on the inside.
I could move the authorizer just after the guard, but I'd lose access to the request attribute of projectName - I don't wish to parse the URL myself to search for the projectName since the URL pattern could change and would break the functionality - i.e. require 2 changes instead of one.
Any ideas how to achieve this?
I would use the standard RoleAuthorizer class to supply the list of allowed roles, along with your custom enroller probably split into two I would then add a custom Filter class that does something like this to call your Enrolers.
protected int beforeHandle(final Request request, final Response response) throws ResourceException {
final String projectName = (String) request.getAttributes().get("projectName");
// Check that a projectName is supplied, should not have got this far otherwise but lets check.
if (projectName == null || projectName.isEmpty()) {
throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND);
}
if (Method.GET.equals(request.getMethod())){
new ReadEnroler(projectName).enrole(request.getClientInfo());
}else{
new MutateEnroler(projectName).enrole(request.getClientInfo());
}
return super.beforeHandle(request, response);
}
the enrolers would then set the appropriate values in the clientInfo.getRoles() Collection when enrole was called.