I want to have a config page, where the user can setup the credentials of the Watson Assistant to use.
Actually, to setup the credentials, you need something similar to:
var assistant = new AssistantV1({
username: process.env.ASSISTANT_USERNAME,
password: process.env.ASSISTANT_PASSWORD,
url: 'https://gateway.watsonplatform.net/assistant/api/',
version: '2018-02-16',
});
and then you do:
assistant.message({
input: { text: newMessageFromUser },
workspace_id: process.env.WORKSPACE_ID,
context : response.context,
}, processResponse)
Obviously, if on my config page the user chose to use another workpace with other IDs, I cannot do again the "const assistant = new AssistantV1" and do another new one with other credentials. I am looking into a way to pass them as parameters when someone changes the configuration on the config page.
In order to do so, is it possible to pass not only the workspace but also the username and password as parameters to assistant.message? If it is possible, how can I do it? What options do I have to change the username & password on the conversation invocation?
Thanks!
The message API is as specified. It works on an instantiated and configure Watson Assistant object.
In your case, you would need to (re-)initialize a new Watson Assistant instance with the user-provided username / password. Also note that Watson Assistant for authentication is moving away from username / password to IAM (Identity and Access Management) bearer token. You need to keep this in mind because not all users might be able to provide what you ask for.
Related
I would like to implement this authentication flow in Keycloak:
A user creates an account by typing only his email
The user is logged in and can access my service
2'. At the same time, an email is sent to him, allowing him to "finalize" his account
The user leaves his session -> to reuse my service, he must click in the received email
By clicking in the received email, the user defines his first password
The user is then logged in automatically (without going through a login page).
The objective of this flow is to be the simplest, to hook users who are not used to webapps.
The implementation I would do:
Create an account without password request: I customize the Keycloak Registration flow by disabling the Password Validation and Profile Validation rules
Programmatically, in my webapp, at the first connection of a user, via the REST Admin API, I trigger the email action UPDATE_PASSWORD
I get something that works, but:
A. The link received by email redirects to an intermediary page confirming the execution of actions ("Perform the following action (s)") - (similar to Keycloak Implement Reset password flow same as forgot password flow)
B. The user is then redirected to a login page, and not directly connected to the application.
When, as a normal user, I trigger a reset password request (through 'forget password' feature), the process is the one I want: by clicking on the email link, I go directly to the page allowing me to enter and confirm a new password, then I'm authenticated.
My question: Do you see a way to implement this 'simplified' flow?
My keycloak version : 11.0.2
Thank you !
I could remove the "info.ftl" page display, customizing the "ExecuteActionsActionTokenHandler", as explained here :
action-token-spi
You have to create a file :
src/main/resources/META-INF/services/org.keycloak.authentication.actiontoken.ActionTokenHandlerFactory
containing the name of the class you want to use instead :
com.example.ExecuteActionTokenHandlerFactory
Then you create that class com.example.ExecuteActionTokenHandlerFactory with the following code :
public class ExecuteActionTokenHandlerFactory extends ExecuteActionsActionTokenHandler {
#Override
public Response handleToken(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) {
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(),
tokenContext.getRealm(), authSession.getClient());
if (redirectUri != null) {
authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true");
authSession.setRedirectUri(redirectUri);
authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
}
token.getRequiredActions().stream().forEach(authSession::addRequiredAction);
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), tokenContext.getUriInfo(), tokenContext.getEvent());
return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction);
}
}
Actually it is the same implementation as the upper class, except we removed the following part :
if (tokenContext.isAuthenticationSessionFresh()) {
...
}
which means that if the user did not have a session, which happens when the user is reseting his password, he is redirected to that "info.ftl" page.
As a workaround for problem A, I customize info.ftl template page. I add an ugly inline script to click on the link, redirecting automatically to the update password page.
<#import "template.ftl" as layout>
(...)
<#elseif actionUri?has_content>
<p><a id="yolo" href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
<script>document.getElementById('yolo').click()</script>
(...)
It'll do the job until I found a cleaner solution.
At the moment, B problem remains.
I have integrated google assistant with my dialogflow agent. I need to get the user who is invoking the intent.
For eg, If an user account "ABC" have access to invoke my agent via Google assistant app, on the welcome intent I have to send a response like "Welcome ABC". How do I achieve this with google assistant app is my endpoint.
Thanks in Advance.
You have two questions here: How to get the user's name and how to get their id.
The first thing to realize is that this information is considered personally identifiable information (PII), so Google doesn't give it to you without the permission of the user. How you ask for that permission, and how it is delivered to you, depends on some of your exact needs.
User ID
Historically, you could get an anonymous user ID for the Assistant account. This would be different than the Google User ID that is available below and was meant to be a persistent identifier so you could keep track of returning users.
This has been deprecated, and if this is all you need, then you can create your own identifier and save it as part of the userStorage.
Requesting user information
The traditional way of getting their name is to request the user for permission to access their information. If you're using the actions-on-google library, you do this using the Permission object with something like this:
const options = {
// We just want permission to get their name
permissions: ['NAME'],
// Prompt them why we want the information
context: 'To address you by name'
};
conv.ask(new Permission(options));
If the user grants permission, the results will be available in conv.user.name. You should save this in the userStorage, since the permission is not persistent. So this might look something like:
var userStorageStr = conv.user.userStorage || '{}';
var userStorage = JSON.parse( userStorageStr );
var name = conv.user.name || userStorage.name;
userStorage.name = name;
// ...
conv.user.userStorage = JSON.stringify( userStorage );
With the multivocal library, you would indicate that the User/Name environment property is one of the Requirements for the action or intent you want. So this might be in your configuration as
Local: {
en: {
Requirements: {
"Action.multivocal.welcome": "User/Name"
}
}
}
The name will be available in the environment under User/Name.
If you're using JSON, then you need to use the user information helper. For Dialogflow, this would be under the payload.google.systemIntent property, while for the Actions SDK this would be in expectedInputs[0].possibleIntents[0]. You might specify something like this:
{
"intent": "actions.intent.PERMISSION",
"inputValueData": {
"#type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
"optContext": "To address you by name",
"permissions": [
"NAME"
]
}
}
The name will be under the originalDetectIntentRequest.payload.user.profile field if you are using Dialogflow and user.profile for the Action SDK.
All of this seems like a lot, just to get a name. And you can't get the email address if you want that in addition. But there are other options.
Requesting their Google Profile
Their Google Profile contains both their unique Google ID, their full name (in the "name" field, given_name, last_name, and typically some other information such as their email address (the email address isn't guaranteed since they can omit this from their profile, but is typically there). You would use Google Sign-In for the Assistant to request this information. There is some configuration required in the Action console, and then you would request permission to get it using the sign-in helper.
With the actions-on-google library, the line would be something like:
conv.ask(new SignIn());
Once the user granted it, you can get their profile in
conv.user.profile.payload
their name in
conv.user.profile.payload.name
and their email in, you guessed it,
conv.user.profile.payload.email
Note that unlike asking for the user information, the profile will be available in all future activity with you. You don't need to store it.
With multivocal, you would say that the User/IsAuthenticated environment setting is one of the Requirements for the action or intent you want. So this might be in your configuration as
Local: {
en: {
Requirements: {
"Action.multivocal.welcome": "User/IsAuthenticated"
}
}
}
The profile will be available in the environment under User/Profile, the name would be in User/Profile/name, and the email in User/Profile/email.
If you're using JSON, then you need to use the sign-in helper. For Dialogflow, this would be under the payload.google.systemIntent property, while for the Actions SDK this would be in expectedInputs[0].possibleIntents[0]. You might specify something like this:
{
"intent": "actions.intent.SIGN_IN",
"inputValueData": {}
}
You will get an identity token for the user in the originalDetectIntentRequest.payload.user.idToken field if you are using Dialogflow and user.idToken for the Action SDK. You will need to validate and decode this JWT. (The actions-on-google and multivocal libraries handle this step for you.)
The easiest would be to use Google Sign-In for the Assistant: https://developers.google.com/actions/identity/google-sign-in
Per the recommendation in the defaultauth sample, I am trying to access the directory api for a domain which I have created a service account for. Here is the code I am attempting to connect with:
import { google } from 'googleapis'
const authClient = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/admin.directory.user.readonly']
})
const service = google.admin('directory_v1')
console.log(
await service.users.list({
auth: authClient,
domain: <redacted>
})
)
However, when I attempt to connect I recieve an error saying Error: Not Authorized to access this resource/api. If I remove the creds.json file in ~/.google, the error changes to saying that it cannot find the credentials file. Also, I am able to access a bucket using the same file, so I'm pretty sure my local environment is set up correctly, authentication wise. I have also worked for the past few days with someone on the support team G Suite API team, who assures me that things are set up correctly on my domain.
After looking around online, it seems the thing I am missing is impersonating an admin account when trying to connect with my service-account. I have found a few examples online of doing this with a JWT auth strategy, but I would like to continue to use the default auth client, in order to abstract away the implementation details. Is this possible? If so, what do I have to change? I have tried setting subject, and delegationEmail in both of the calls (getClient and list).
Any help would be greatly appreciated.
Just set subject of the client object:
authClient.subject = 'your email address'
Google's api documentations highly varies by language. No standart. Something documented in PHP client may be missing in nodejs client and it can take hours to find out how to do it.
You can pass clientOptions.subject in the constructor.
import { google } = from 'googleapis';
const authClient = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/admin.directory.user.readonly'],
clientOptions: {
subject: "your email address"
});
I've implemented the implicit flow for Actions on Google account linking, and am using Dialogflow (previously API.AI) to define intents.
The full flow works in the device simulator (from AOG). The first intent gets a "It looks like your account isn't linked yet..." response, and the debug pane includes a URL to initiate linking:
https://assistant.google.com/services/auth/handoffs/auth/start?account_name=[account]#gmail.com&provider=[project_id]_dev&scopes=email&return_url=https://www.google.com/
If I follow this URI in a cache-less window:
I'm redirected to my app's authentication page
I choose to sign in with my Google account (same as [account] above)
I'm redirected to google.com with a success message in the URI bar
The simulator now accepts actions via my app and responds correctly
However, if I follow the same flow using a physical Google Home & the gH app for Android.
Device tells me account not yet linked
Open Google home and follow 'Link to [my app]' link
Browser opens to authentication page
Sign in as user
Redirected to a white page with a single link "Return to app", which has an href: about:invalid#zClosurez
Linking was unsuccessful, so additional attempts to run intents on the Google Home get the same "Account not yet linked" response.
I've inspected the intermediate access_token and state variables at length, and they all match and look to be correctly formatted:
Authentication URL (app sign in page): https://flowdash.co/auth/google?response_type=token&client_id=[client_id]&redirect_uri=https://oauth-redirect.googleusercontent.com/r/[project_id]&scope=email&state=[state]
After authenticating, redirected to (this is the white screen with 'return to app' broken link): https://oauth-redirect.googleusercontent.com/r/genzai-app#access_token=[token]&token_type=bearer&state=[state]
So, it seems there's something non-parallel about the way the simulator and physical devices work in terms of implicit flow account linking.
I've been struggling with this, and with the AOG support team for a very long time to no avail. Anyone else see a similar issue?
Updated with response redirect code:
Login handled by react-google-login component with profile & email scopes. On success we call:
finish_auth(id_token) {
let provider = {
uri: '/api/auth/google_auth',
params: ['client_id', 'redirect_uri', 'state', 'response_type'],
name: "Google Assistant"
}
if (provider) {
let data = {};
provider.params.forEach((p) => {
data[p] = this.props.location.query[p];
});
if (id_token) data.id_token = id_token;
api.post(provider.uri, data, (res) => {
if (res.redirect) window.location = res.redirect;
else if (res.error) toastr.error(res.error);
});
} else {
toastr.error("Provider not found");
}
}
provider.uri hits this API endpoint:
def google_auth(self):
client_id = self.request.get('client_id')
redirect_uri = self.request.get('redirect_uri')
state = self.request.get('state')
id_token = self.request.get('id_token')
redir_url = user = None
if client_id == DF_CLIENT_ID:
# Part of Google Home / API.AI auth flow
if redirect_uri == "https://oauth-redirect.googleusercontent.com/r/%s" % secrets.GOOGLE_PROJECT_ID:
if not user:
ok, _email, name = self.validate_google_id_token(id_token)
if ok:
user = User.GetByEmail(_email, create_if_missing=True, name=name)
if user:
access_token = user.aes_access_token(client_id=DF_CLIENT_ID)
redir_url = 'https://oauth-redirect.googleusercontent.com/r/%s#' % secrets.GOOGLE_PROJECT_ID
redir_url += urllib.urlencode({
'access_token': access_token,
'token_type': 'bearer',
'state': state
})
self.success = True
else:
self.message = "Malformed"
else:
self.message = "Malformed"
self.set_response({'redirect': redir_url}, debug=True)
I am able to make it work after a long time. We have to enable the webhook first and we can see how to enable the webhook in the dialog flow fulfillment docs If we are going to use Google Assistant, then we have to enable the Google Assistant Integration in the integrations first. Then follow the steps mentioned below for the Account Linking in actions on google:-
Go to google cloud console -> APIsand Services -> Credentials -> OAuth 2.0 client IDs -> Web client -> Note the client ID, client secret from there -> Download JSON - from json note down the project id, auth_uri, token_uri -> Authorised Redirect URIs -> White list our app's URL -> in this URL fixed part is https://oauth-redirect.googleusercontent.com/r/ and append the project id in the URL -> Save the changes
Actions on Google -> Account linking setup 1. Grant type = Authorisation code 2. Client info 1. Fill up client id,client secrtet, auth_uri, token_uri 2. Enter the auth uri as https://www.googleapis.com/auth and token_uri as https://www.googleapis.com/token 3. Save and run 4. It will show an error while running on the google assistant, but dont worry 5. Come back to the account linking section in the assistant settings and enter auth_uri as https://accounts.google.com/o/oauth2/auth and token_uri as https://accounts.google.com/o/oauth2/token 6. Put the scopes as https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email and weare good to go. 7. Save the changes.
In the hosting server(heroku)logs, we can see the access token value and through access token, we can get the details regarding the email address.
Append the access token to this link "https://www.googleapis.com/oauth2/v1/userinfo?access_token=" and we can get the required details in the resulting json page.
`accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])`
Not sure which python middleware or modules you are using but
self.set_response({'redirect': redir_url}, debug=True)
seems to be setting parameters for a returning a response which isn't correct. Instead you should redirect your response to the redirect_url. For example importing the redirect module in Flask or Django like:
from flask import redirect or from django.shortcuts import redirect
then redirect like:
return redirect(redirect_url)
It appears Google has made a change that has partially solved this problem in that it is now possible to complete the implicit account linking flow outside of the simulator, in the way outlined in my question.
It seems the problem stemmed from an odd handling (on the AOG side) of the client-side redirect case used after sign in with the Google sign-in button.
From Jeff Craig in this thread:
The current workaround, where we provide the "Return to app" link
currently what we're able to provide. The issue is with the way that
redirecting to custom-scheme URIs is handled in Chrome, specifically,
with regard to the redirect happening in the context of a user action.
XHR will break that context, so what is happening is that you click
the Google Sign-In Button, which triggers an XHR to Google's servers,
and then you (most likely) do a client-side redirect back to the
redirect_url we supply, our handler executes, and isn't able to do a
JS redirect to the custom scheme URI of the app, because were outside
of the context of a direct user click.
This is more of a problem with the Implicit (response_type=token) flow
than with the authorization code (response_type=code) flow, and the
"Return to app" link is the best fallback case we currently have,
though we are always looking for better solutions here as well.
The current behavior shows the 'Return to app' link, but as of last week, this link's href is no longer about:invalid#zClosurez, but instead successfully completes the sign-in and linking process. It's an odd and confusing UX that I hope Google will improve in the future, but it was sufficient to get my app approved by the AOG team without any changes to my flow.
I am using "IdentityServer3 - IdentityManager - MembershipReboot" in my project for User Management, Authentication & Resources Authorization.
I started from below sample and have gone good for creating users, authenticating them via /connect/token api and authorizing resources.
https://github.com/thinktecture/Thinktecture.IdentityServer.v3.Samples/tree/master/source/MembershipReboot
A brief architecture for my solution is
MySql as database. Communication via MembershipReboot.EF to MembershipReboot.
The client project is developed using html + angularjs.
Resources APIs are developed using Nancy & hosted on Owin+Katana in a seperate project.
Authentication Services(IdSvr+IdMgr+MR) are hosted in a seperate project.
Now I want to create a simple button/link clicking on which leads me to facebook login. The functionality of this button should be same as defined in IDSvr default login page's(https://localhost:44333/core/login?signin=4f909a877cc465afd26d72f60ec08f51) "Facebook button".
I have tried googled internet a lot but none of cases are matching my scenario.
I even tried to replicate the request-response behaviour of default IdSvr facebook login but that does not work as cookies are not being saved on end client.
Also i tried to hit "https://localhost:44333/core/signin-facebook" and getting response as HTTP/1.1 500 Internal Server Error from server. So i think might be I am somewhere wrong in setting facebook options in IdSrv project.
So if someone can just provide me a single IdSvr API to connect or tell me how to config Id Svr so that mapping a url can redirect it to facebook login. Or can tell me that where I am wrong in setting facebook authentication options in IdSrv.
A short and simple answer for my question is that I was looking for url.
https://localhost:44333/connect/authorize?client_id=implicitclient&response_type=token&scope=read&redirect_uri=http://localhost:8088/login/auth&nonce=random_nonce&acr_values=idp%3AFacebook&response_mode=form_post
Read further if you want to get better idea about this url
After lots of Hit&Trial & Study efforts, I have got solution for this. Well I think root cause for this problem was that sudden new technical things(Owin, Katana, OAuth, IdentityServer, IdentityManagement, MembershipReboot, Owin Facebook) and a meager time to understand them all.
I would advice folks that whoever is in same situation as me then first get an idea about OAuth. I found below link as a short and good one.
http://tutorials.jenkov.com/oauth2/index.html
After this I learnt that in our scenario we are dealing with two applications and hence two authentication.
For connecting User to Facebook. We created an app on developers.facebook.com
For connecting User to IdentityServer. We created a client in Clients.cs file on AuthenticationServices project.
So now here is the final solution.
localhost:44333 where AuthenticationService is running
locahost:8088 where FrontEnd services are running which iscalling AuthenticationService .
1. Create client app in AuthenticationServices as below
new Client
{
ClientName = "Implicit Clients",
Enabled = true,
ClientId = "implicitclient",
ClientSecrets = new List<ClientSecret>{
new ClientSecret("secret".Sha256())
},
Flow = Flows.Implicit,
RequireConsent = true,
AllowRememberConsent = true,
RedirectUris = new List<string>
{
"http://localhost:8088/login/auth" //This should be redirect url you want to hit after your app(not facebook app) redirects.
},
ScopeRestrictions = new List<string>
{
Constants.StandardScopes.OpenId,
Constants.StandardScopes.Profile,
Constants.StandardScopes.Email,
"read",
"write",
},
//SubjectType = SubjectTypes.Global,
AccessTokenType = AccessTokenType.Jwt,
IdentityTokenLifetime = 360,
AccessTokenLifetime = 360,
},
2 Create Authorize URL as below
var client = new OAuth2Client(new Uri("https://localhost:44333/core/connect/authorize"));
var startUrl = client.CreateAuthorizeUrl(
clientId: "implicitclient",
responseType: "token",
scope: "read",
redirectUri: "http://localhost:8088/login/auth",
nonce: "random_nonce",
responseMode: "form_post",
acrValues: "idp:Facebook");
The facebook app after successful authorization will redirect default to http://localhost:44333/signin-facebook. So no need to do any changes there.
Finally on http://localhost:8088/login/auth you will get access_token(+ few other parameters) after successful authentication. Here onwards you can use this token to access resources from Resources server.