Adding name to identity token instead of access token - next-auth

Im trying to attach some user data for display purposes to my identity token in duende identityserver but only succeed in attaching it to my access token.
I have setup my identity server with aspnet identity as the provider.
By default my retrieved identity token and access token are returned as follows:
(id token)
{
"iss": "https://localhost:5001",
"nbf": 1676383755,
"iat": 1676383755,
"exp": 1676384055,
"aud": "frontend",
"amr": [
"pwd"
],
"at_hash": "3TXV8nYmX5Ukn-bmuLPLvQ",
"sid": "B4FA078C6687CA5F7B1BE31615517880",
"sub": "c62bd642-3096-43aa-833f-966103dd3071",
"auth_time": 1676383754,
"idp": "local"
}
(access token)
{
"iss": "https://localhost:5001",
"nbf": 1676383755,
"iat": 1676383755,
"exp": 1676387355,
"scope": [
"openid",
"profile"
],
"amr": [
"pwd"
],
"client_id": "frontend",
"sub": "c62bd642-3096-43aa-833f-966103dd3071",
"auth_time": 1676383754,
"idp": "local",
"sid": "B4FA078C6687CA5F7B1BE31615517880",
"jti": "F9FC411122610B702E27039A7D046698"
}
So no userinfo yet so i decided to implement the IProfileService in my identity server solution:
public class ProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<IdentityUser> _userClaimsPrincipalFactory;
private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public ProfileService(IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory, UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userClaimsPrincipalFactory = userClaimsPrincipalFactory;
_userManager = userManager;
_roleManager = roleManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);
var claims = userClaims.Claims.ToList();
claims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
claims.Add(new Claim(JwtClaimTypes.Name, "testname"));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
and now my access token have the "name" claim
{
"iss": "https://localhost:5001",
"nbf": 1676384026,
"iat": 1676384026,
"exp": 1676387626,
"scope": [
"openid",
"profile"
],
"amr": [
"pwd"
],
"client_id": "frontend",
"sub": "c62bd642-3096-43aa-833f-966103dd3071",
"auth_time": 1676384025,
"idp": "local",
"name": "testname",
"sid": "E7E89A3E6A100C2115ED15BDE17CBE66",
"jti": "80BD171F1AD0F128DA756600D6B96DCE"
}
Should this not be added to the id token instead of the access token? It is my understanding that the id token holds the responsibility for parameters such as name, email etc?
So i guess the question is how I get profile information attached to my id token?
Tried adding profile as default for my client in my identityserver config but now profile information is added to the identity (or access) token:
public class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> {"role"}
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new[]
{
new ApiScope("testapi.read"),
new ApiScope("testapi.write")
};
public static IEnumerable<ApiResource> ApiResources =>
new[]
{
new ApiResource("testapi")
{
Scopes = new List<string> { "testapi.read", "testapi.write"},
ApiSecrets = new List<Secret> {new Secret("Secret".Sha256())},
UserClaims = new List<string> {"role", IdentityServerConstants.StandardScopes.Profile }
}
};
public static IEnumerable<Client> Clients =>
new[]
{
new Client
{
ClientId = "m2m.client",
ClientName = "m2m credentials client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("ClientSecret".Sha256())},
AllowedScopes = { "testapi.read", "testapi.write" }
},
new Client
{
ClientId = "frontend",
ClientSecrets = { new Secret("ClientSecret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "http://localhost:3000/api/auth/callback/frontend"},
FrontChannelLogoutUri = "http://localhost:3000/signout-oidc",
PostLogoutRedirectUris = { "http://localhost:3000/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "testapi.read", "testapi.write" },
RequirePkce = true,
RequireConsent= false,
AllowPlainTextPkce = false
}
};
}

Related

How to get the roles in Auth0's user management to be added in the JWT?

I have an Auth0 application and I'm maintaining roles through the User Management. I would like to get those roles that are assigned to a user to be added to the JWT returned.
I do have the following in the openid_connect_configuration.conf
map $host $oidc_scopes {
default "openid+profile+email+offline_access+openid roles";
}
i have the following in the /.well-known/openid-configuration
{
...
"scopes_supported": [
"openid",
"profile",
"offline_access",
"name",
"given_name",
"family_name",
"nickname",
"email",
"email_verified",
"picture",
"created_at",
"identities",
"phone",
"address"
],
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token"
],
"code_challenge_methods_supported": [
"S256",
"plain"
],
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": [
"HS256",
"RS256"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post"
],
"claims_supported": [
"aud",
"auth_time",
"created_at",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"identities",
"iss",
"name",
"nickname",
"phone_number",
"picture",
"sub"
],
"request_uri_parameter_supported": false
}
How do I set things in Auth0 to return the roles assigned to the logged in user? I have tried looking into the documentation, but I had no luck.
I found my answer through exploring the extensions in Auth0. I installed the Auth0 Authorization extension. I enabled the groups and roles.
I then added the following rule:
function setRolesToUser(user, context, callback) {
// Roles should only be set to verified users.
if (!user.email || !user.email_verified) {
return callback(null, user, context);
}
user.app_metadata = user.app_metadata || {};
auth0.users
.updateAppMetadata(user.user_id, user.app_metadata)
.then(function () {
context.idToken['https://example.com/auth'] = user.app_metadata.authorization;
callback(null, user, context);
})
.catch(function (err) {
callback(err);
});
}
I get the following as the JWT payload:
{
"https://example.com/auth": {
"groups": ["Samples"],
"roles": ["Editor"]
},
"sub": "auth0|xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"nickname": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"email_verified": true,
"iss": "https://dev-xxxxxxxxxx.us.auth0.com/",
"updated_at": "2022-04-29T20:01:14.585Z",
"iat": 1.651330616E9,
"picture": "https://s.gravatar.com/avatar/a705adb3d5d8530c35c41a9de260cd3c?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Flo.png",
"exp": 1.651366616E9,
"name": "xxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxx",
"aud": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"nonce": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"email": "xxxxx.xxxxxxxxx#example.com"
}

Flutter Amplify Cognito, no tokens using fetchAuthSession

I'm trying to implement authentication in my Flutter app using Cognito. I'm authenticating against an existing userPool which I've been successfully using for the past year in my React app.
However, with Flutter I'm not able to fetch the user's session. I'm able to login successfully but I'm unable to get any tokens using the fetchAuthSession() method. Any idea why this is happening? Here is some of my working and non-working code:
This code is successful...
Future _usersEmail() async {
try {
var attributes = (await Amplify.Auth.fetchUserAttributes()).toList();
for (var attribute in attributes) {
if (attribute.userAttributeKey == 'email') {
print("user's email is ${attribute.value}");
return '${attribute.value}';
}
}
return 'no email';
} on AuthException catch (e) {
return '${e.message}';
}
}
This code is successful too...
Future<bool> _isSignedIn() async {
final CognitoAuthSession session =
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
print('_isSignedIn: ${session.isSignedIn}');
return session.isSignedIn;
}
This code return null...
Future _getIdToken() async {
final CognitoAuthSession session =
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
final idToken = session.userPoolTokens?.idToken;
print('idToken: $idToken');
return idToken;
}
Here is my amplifyconfig...
{
"UserAgent": "aws-amplify-cli/2.0",
"Version": "1.0",
"auth": {
"plugins": {
"awsCognitoAuthPlugin": {
"UserAgent": "aws-amplify-cli/0.1.0",
"Version": "0.1.0",
"IdentityManager": {
"Default": {}
},
"CredentialsProvider": {
"CognitoIdentity": {
"Default": {
"PoolId": "us-east-1_abcxyz",
"Region": "us-east-1"
}
}
},
"CognitoUserPool": {
"Default": {
"PoolId": "us-east-1_abcxyz",
"AppClientId": "5j0kii90dJ09est43xh3X21",
"Region": "us-east-1"
}
},
"Auth": {
"Default": {
"authenticationFlowType": "USER_SRP_AUTH"
}
}
}
}
}
}
you might need to set getAWSCredentials to true in your options parameter like so:
final authSession = (await Amplify.Auth.fetchAuthSession(
options: CognitoSessionOptions(getAWSCredentials: true),
)) as CognitoAuthSession;

Why is Quarkus JWT Returning Unauthorized on Every Endpoint

I'm trying to secure my Quarkus API with JWT. The JWT is provided (snippet: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUI[...] ).
The following endpoints are the 2 endpoints I've tested:
#Path("/quiz")
#RequestScoped
public class SomeResource {
#Inject
JsonWebToken jwt;
#POST
#RolesAllowed({"magister"})
#Path("/save")
#Consumes("application/json")
#Produces("*/*")
#Transactional
public Response save(#RequestBody Quiz quiz) { }
#GET
#PermitAll
#Path("/get/all")
#Produces("application/json")
public Response getAll(){ }
Both endpoints (#PermitAll and #RolesAllowed) are returning me an HTTP 401 (Unauthorized).
Do you have an idea why? I thought that #PermitAll is permitting EVERY request? Even though my token proves I have the role needed:
"resource_access" : {
"client_interface" : {
"roles" : ["magister"]
},
...
}
Edit:
Found out that the MicroProfile Spec says that
"groups":["magister"]
should get mapped by microprofile to RolesAllowed annotations.
My Payload looks like this:
{
[...]
"resource_access": {
"client_interface": {
"roles": [
"magister"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email",
"email_verified": false,
"groups": [
"magister"
],
"preferred_username": "magister"
}
but I'll still get 401 Response
I had the same problem, I fixed it by adding the following code:
#OpenAPIDefinition(
info = #Info(
title = "Title API",
version = "1.0.0",
description = "Description API"
),
security = #SecurityRequirement(name = "jwt"),
components = #Components(
securitySchemes = {
#SecurityScheme(
securitySchemeName = "jwt",
description = "Token JWT",
type = SecuritySchemeType.HTTP,
scheme = "bearer",
bearerFormat = "jwt"
)
}
)
)
and also made an update Quarkus to version 1.12.0.FINAL
Generally 401 is about using a expired token, or a invalid one.

Swashbuckle - Swagger execute button is not working

I am trying to integrate swagger in Asp.Net core 3.1 Web API using Swashbuckle.AspNetCore (5.5.1) with OAS3.
I am having one post method where I need multipart form data (two files and one string value) and for that I have applied below OperationFilter, because I don't want to specify any parameters at action level.
public class ComparePostParamTypes : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var listOfOutputFormats = new List<string> { "Rtf", "Doc", "DocX", "Pdf" };
var optionArray = new OpenApiArray();
optionArray.AddRange(listOfOutputFormats.Select(s => new OpenApiString(s)));
string documentOutputFormatText =
"The format to return";
switch (operation.OperationId)
{
case "File_Post":
operation.Parameters.Clear();
operation.Parameters = new List<OpenApiParameter>
{
new OpenApiParameter
{
Name = "file1", In = ParameterLocation.Query,
Required = true,
Description = "First Document",
Schema = new OpenApiSchema()
{
Type="string",
Format="binary"
}
},
new OpenApiParameter
{
Name = "file2", In = ParameterLocation.Query,
Required = true,
Description = "Second Document",
Schema = new OpenApiSchema()
{
Type="string",
Format="binary"
}
},
new OpenApiParameter
{Name = "outputFormat", In = ParameterLocation.Query, Description = documentOutputFormatText,
Schema = new OpenApiSchema()
{
Type="string",
Enum = optionArray,
Default = new OpenApiString("Rtf"),
}
}
};
break;
}
}
}
This is my controller endpoint
/// <summary>
/// POSTing two documents as a multipart/form-data.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>The result in the specified format (see outputFormat parameter)</returns>
/// <remarks>
/// Pass two document and output format</remarks>
/// <response code="200">OK</response>
/// <response code="500">Internal error</response>
/// <response code="403">Forbidden</response>
/// <response code="422">UnprocessableEntity</response>
/// <response code="503">ServiceUnavailable</response>
/// <response code="400">BadRequest</response>
[Produces("application/pdf", "application/msword", "application/zip")]
[Consumes("multipart/form-data")]
[ProducesResponseType(StatusCodes.Status200OK, Type = null)]
[ProducesResponseType(StatusCodes.Status500InternalServerError, Type = null)]
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = null)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = null)]
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable, Type = null)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = null)]
[HttpPost(Name ="File_Post")]
public IActionResult Post()
{
var builBoundary = Request.GetMultipartBoundary();
return Ok(builBoundary);
}
Correct Swagger UI is rendered
Swagger UI
But when I clicked on execute button after attaching files nothing happened.
This is generated swagger JSON
{
"openapi": "3.0.1",
"info": {
"title": "Demo",
"version": "v1"
},
"paths": {
"/File": {
"post": {
"tags": [
"File"
],
"summary": "POSTing two documents as a multipart/form-data.",
"description": "Pass two document and output format",
"operationId": "File_Post",
"parameters": [
{
"name": "file1",
"in": "query",
"description": "First Document",
"required": true,
"schema": {
"type": "string",
"format": "binary"
}
},
{
"name": "file2",
"in": "query",
"description": "Second Document",
"required": true,
"schema": {
"type": "string",
"format": "binary"
}
},
{
"name": "outputFormat",
"in": "query",
"description": "The format to return",
"schema": {
"enum": [
"Rtf",
"Doc",
"DocX",
"Pdf"
],
"type": "string",
"default": "Rtf"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal error"
},
"403": {
"description": "Forbidden"
},
"422": {
"description": "UnprocessableEntity"
},
"503": {
"description": "ServiceUnavailable"
},
"400": {
"description": "BadRequest"
}
}
}
}
},
"components": { }
}
Please tell me what should I do to fix this.
I am able to fix this by updating OperationFilter
public class ComparePostParamTypes : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var listOfOutputFormats = new List<string> { "Rtf", "Doc", "DocX", "Pdf" };
var optionArray = new OpenApiArray();
optionArray.AddRange(listOfOutputFormats.Select(s => new OpenApiString(s)));
string documentOutputFormatText =
"The format to return";
switch (operation.OperationId)
{
case "File_Post":
var multipartBodyPost = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
Properties =
{
["file1"] = new OpenApiSchema
{
Description = "First Document",
Type = "string",
Format = "binary"
},
["file2"] = new OpenApiSchema
{
Description = "Second Document",
Type = "string",
Format = "binary"
},
["outputFormat"] = new OpenApiSchema
{
Description = documentOutputFormatText,
Type = "string",
Enum = optionArray,
Default = new OpenApiString("Rtf"),
},
},
Required = { "file1", "file2" }
}
};
operation.RequestBody = new OpenApiRequestBody
{
Content =
{
["multipart/form-data"] = multipartBodyPost
}
};
break;
}
}
}
I more details, check this link https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1782

Storing data "across conversations" in Google Action

I'm a bit confused by the Google Actions documentation about storing data and hoped someone can help clarify...
The docs state that data in the conv.user.storage object will be saved "across conversations". I took this to mean that if the user exited the conversation these values would be persisted and available the next time they interact with my action. Is that understanding correct?
The reason I ask is that I can't get this behaviour to work in my action.
I have built a simple action fulfilment service (using Actions on Google NodeJS library v2.4.0 and Koa v2.5.3). The fulfilment is triggered from an intent defined in Dialogflow (after an account has been linked with Google Sign In) and stores a value in conversation storage. The code is as follows:
server.js (base server - loads actions dynamically from the local ./actions/ dir)
/* Load the environment */
const dotenv = require('dotenv');
const path = require('path');
const packageJson = require('./package.json');
dotenv.config({
silent: true,
path: process.env.ENV_FILE!=undefined && process.env.ENV_FILE.trim()!='' ? path.normalize(process.env.ENV_FILE) : path.join(__dirname, './.env')
});
const SERVER_NAME = process.env.NAME || packageJson.name;
const SERVER_PORT = process.env.PORT||'8080';
const SERVER_HOST = process.env.HOST||'0.0.0.0';
const HANDLERS_PATH = './actions/';
/* Load the dependencies */
const logger = require('utils-general').logger('google-server');
const Koa = require('koa');
const KoaBody = require('koa-body');
const KoaActionsOnGoogle = require('koa-aog');
const fs = require('fs');
const { dialogflow } = require('actions-on-google');
/* Load and initialise the Google Assistant actions */
//Initialise DialogFlow
const action = dialogflow({ debug: process.env.ACTIONS_DEBUG==='true', clientId: process.env.GOOGLE_CLIENT_ID });
//Load the action intent handlers
const handlers = [];
let handlerFiles = fs.readdirSync(HANDLERS_PATH);
handlerFiles.forEach(function loadHandlers(file) {
let handlerImpl = require(HANDLERS_PATH+file);
let handler = {};
handler[handlerImpl.intent] = handlerImpl.action;
handlers.push(handler);
});
//Add the actions intent handlers to DialogFlow
handlers.forEach(item => {
let key = Object.keys(item)[0];
logger.info(`Adding handler for action intent ${key}`);
action.intent(key, item[key]);
});
/* Create the application server to handle fulfilment requests */
logger.info(`Initialising the ${SERVER_NAME} server (port: ${SERVER_PORT}, host: ${SERVER_HOST})`);
//Create the server
const app = new Koa();
//Add default error handler middleware
app.on('error', function handleAppError(err) {
logger.error(`Unhandled ${err.name||'Error'}: ${err.message || JSON.stringify(err)}`);
});
//Add body parsing middleware
app.use(KoaBody({ jsonLimit: '50kb' }));
//Log the request/ response
app.use(async (ctx, next) => {
logger.trace(`REQUEST ${ctx.method} ${ctx.path} ${JSON.stringify(ctx.request.body)}`);
await next();
logger.trace(`RESPONSE (${ctx.response.status}) ${ctx.response.body ? JSON.stringify(ctx.response.body) : ''}`);
});
//Make the action fulfilment endpoint available on the server
app.use(KoaActionsOnGoogle({ action: action }));
/* Start server on the specified port */
app.listen(SERVER_PORT, SERVER_HOST, function () {
logger.info(`${SERVER_NAME} server started at ${new Date().toISOString()} and listening for requests on port ${SERVER_PORT}`);
});
module.exports = app;
storage-read.js (fulfilment for the "STORAGE_READ" intent - reads stored uuid from conversation storage):
const logger = require('utils-general').logger('google-action-storage-read');
const { SimpleResponse } = require('actions-on-google');
const { getUserId } = require('../utils/assistant-util');
const _get = require('lodash.get');
module.exports = {
intent: 'STORAGE_READ',
action: async function (conv, input) {
logger.debug(`Processing STORAGE_READ intent request: ${JSON.stringify(conv)}`, { traceid: getUserId(conv) });
let storedId = _get(conv, 'user.storage.uuid', undefined);
logger.debug(`User storage UUID is ${storedId}`);
conv.close(new SimpleResponse((storedId!=undefined ? `This conversation contains stored data` : `There is no stored data for this conversation`)));
}
}
storage-write.js (fulfils the "STORAGE_WRITE" intent - writes a UUID to conversation storage):
const logger = require('utils-general').logger('google-action-storage-read');
const { SimpleResponse } = require('actions-on-google');
const { getUserId } = require('../utils/assistant-util');
const _set = require('lodash.set');
const uuid = require('uuid/v4');
module.exports = {
intent: 'STORAGE_WRITE',
action: async function (conv, input) {
logger.debug(`Processing STORAGE_WRITE intent request`, { traceid: getUserId(conv) });
let newId = uuid();
logger.debug(`Writing new UUID to conversation storage: ${newId}`);
_set(conv, 'user.storage.uuid', newId);
conv.close(new SimpleResponse(`OK, I've written a new UUID to conversation storage`));
}
}
This "STORAGE_WRITE" fulfilment stores the data and makes it available between turns in the same conversation (i.e. another intent triggered in the same conversation can read the stored data). However, when the conversation is closed, subsequent (new) conversations with the same user are unable to read the data (i.e. when the "STORAGE_READ" intent is fulfilled) - the conv.user.storage object is always empty.
I have voice match set up on the Google account/ Home Mini I'm using, but I can't see how I determine in the action if the voice is matched (although it seems to be as when I start a new conversation my linked account is used). I'm also getting the same behaviour on the simulator.
Sample request/ responses (when using the simulator) are as follows:
STORAGE_WRITE request:
{
"user": {
"userId": "AB_Hidden_EWVzx3q",
"locale": "en-US",
"lastSeen": "2018-10-18T12:52:01Z",
"idToken": "eyMyHiddenTokenId"
},
"conversation": {
"conversationId": "ABwppHFrP5DIKzykGIfK5mNS42yVzuunzOfFUhyPctG0h0xM8p6u0E9suX8OIvaaGdlYydTl60ih-WJ5kkqV4acS5Zd1OkRJ5pnE",
"type": "NEW"
},
"inputs": [
{
"intent": "actions.intent.MAIN",
"rawInputs": [
{
"inputType": "KEYBOARD",
"query": "ask my pathfinder to write something to conversation storage"
}
],
"arguments": [
{
"name": "trigger_query",
"rawText": "write something to conversation storage",
"textValue": "write something to conversation storage"
}
]
}
],
"surface": {
"capabilities": [
{
"name": "actions.capability.WEB_BROWSER"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
}
]
},
"isInSandbox": true,
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.WEB_BROWSER"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
}
],
"requestType": "SIMULATOR"
}
STORAGE_WRITE response:
{
"conversationToken": "[]",
"finalResponse": {
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "OK, I've written a new UUID to conversation storage"
}
}
]
}
},
"responseMetadata": {
"status": {
"message": "Success (200)"
},
"queryMatchInfo": {
"queryMatched": true,
"intent": "a7e54fcf-8ff1-4690-a311-e4c6a8d1bfd7"
}
},
"userStorage": "{\"data\":{\"uuid\":\"7dc835fa-0470-4028-b8ed-3374ed65ac7c\"}}"
}
Subsequent STORAGE_READ request:
{
"user": {
"userId": "AB_Hidden_EWVzx3q",
"locale": "en-US",
"lastSeen": "2018-10-18T12:52:47Z",
"idToken": "eyMyHiddenTokenId"
},
"conversation": {
"conversationId": "ABwppHHVvp810VEfa4BhBJPf1NIfKUGzyvw9JCw7kKq9YBd_F8w0VYjJiSuzGLrHcXHGc9pC6ukuMB62XVkzkZOaC24pEbXWLQX5",
"type": "NEW"
},
"inputs": [
{
"intent": "STORAGE_READ",
"rawInputs": [
{
"inputType": "KEYBOARD",
"query": "ask my pathfinder what is in conversation storage"
}
],
"arguments": [
{
"name": "trigger_query",
"rawText": "what is in conversation storage",
"textValue": "what is in conversation storage"
}
]
}
],
"surface": {
"capabilities": [
{
"name": "actions.capability.WEB_BROWSER"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
}
]
},
"isInSandbox": true,
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.WEB_BROWSER"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
}
],
"requestType": "SIMULATOR"
}
STORAGE_READ response:
{
"conversationToken": "[]",
"finalResponse": {
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "There is no stored data for this conversation"
}
}
]
}
},
"responseMetadata": {
"status": {
"message": "Success (200)"
},
"queryMatchInfo": {
"queryMatched": true,
"intent": "368d08d3-fe0c-4481-aa8e-b0bdfa659eeb"
}
}
}
Can someone set me straighten me out on whether I'm misinterpreting the docs or maybe I have a bug somewhere?
Thanks!
my suspicion is that personal results are turned off in your case.
You mentioned you're testing on Home Mini and Prisoner was able reproduce on device (in the comments).
Shared devices like Smart Speakers (Home, Mini) and Smart Displays have personal results disabled by default. Check this documentation to enable it.
Open Settings on your Android phone
Under "Assistant devices," select your device (e.g. Mini)
Turn Personal results on
Beware that this means personal results like Calendar entries can be accessed through the device.
To check if userStorage will persist, you can use the GUEST/VERIFIED flag, see documentation here.