Related
I'm building a simple commandline application in Java, that logs into my email box (IMAP) and downloads all attachments. I used basic authentication, but Microsoft is in the process of disabling that so I try to convert my application to use OAuth instead.
After reading on the different OAuth flows, it seems that for my simple standalone commandline application, where there is no problem to simply hardcode a password, the Resource Owner Password Credentials Grand (as described here) would be the best (or a good) choice. I further based myself on the instructions from this source where it is described how to enable OAuth using recent versions of Javamail.
Putting it all together seems a bit harder, and I keep getting AUTHENTICATE Failed errors.
So, what did I try? I first retrieve my authorization token as follows:
public String getAuthToken() {
try {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
String clientId = "some client UUID";
String scopes = "email openid IMAP.AccessAsUser.All offline_access";
String client_secret = "My client secret, not base64 encoded";
String username = "my emailadress";
String password = "my password, not base64 encoded";
String encodedBody = "client_id=" + clientId
+ "&scope=" + scopes
+ "&client_secret=" + client_secret
+ "&username=" + username
+ "&password=" + password
+ "&grant_type=password";
loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
CloseableHttpResponse loginResponse = client.execute(loginPost);
byte[] response = loginResponse.getEntity().getContent().readAllBytes();
ObjectMapper objectMapper = new ObjectMapper();
JavaType type = objectMapper.constructType(objectMapper.getTypeFactory()
.constructParametricType(Map.class, String.class, String.class));
Map<String, String> parsed = new ObjectMapper().readValue(response, type);
return parsed.get("access_token");
} catch (Exception e) {
e.printStackTrace();
return null;
}
The response from the oauth service is actually a json-object which contains following fields:
Obviously the tokens are much longer, but are not shared here. The access_token itself is in the form of three base64 encoded strings seperated by a . The first, when decoded contains
{
"typ": "JWT",
"nonce": "Vobb8bI7E...",
"alg": "RS256",
"x5t": "2ZQpJ3Up...",
"kid": "2ZQpJ3Up..."
}
the second part is a larger object, containing following fields (redacted as well):
{
"aud": "someuuid",
"iss": "https://sts.windows.net/someuuid/",
"iat": 1658397625,
"nbf": 1658397625,
"exp": 1658402597,
"acct": 0,
"acr": "1",
"aio": "ASQ....",
"amr": [
"pwd"
],
"app_displayname": "myapp",
"appid": "some uuid",
"appidacr": "1",
"family_name": "My Last Name",
"given_name": "My First Name",
"idtyp": "user",
"ipaddr": "some.ip.address.here",
"name": "My Full name",
"oid": "someuuid",
"platf": "14",
"puid": "10032...",
"rh": "0.AToA....",
"scp": "email IMAP.AccessAsUser.All openid profile",
"sub": "enaKK...",
"tenant_region_scope": "EU",
"tid": "someuuid",
"unique_name": "my email",
"upn": "my email",
"uti": "1cc...",
"ver": "1.0",
"wids": [
"some uuid",
"some uuid"
],
"xms_st": {
"sub": "02n7h..."
},
"xms_tcdt": 1571393936
}
The last part is just binary data. I currenly simply pass on the entire access_token as I receive it to JavaMail as follows:
String accesstoken = new OauthTokenFetcher().getAuthToken();
imapReader = new ImapMailBoxReader(
"outlook.office365.com",
"my email",
accesstoken);
LocalDate startDate = LocalDate.of(2022,4,1);
LocalDate endDate = LocalDate.of(2022,7,1);
imapReader.processOnMessages("Inbox", startDate, endDate,this::processMessage);
with ImapMailBoxReader as follows:
public class ImapMailBoxReader {
private String host;
private String username;
private String password;
public ImapMailBoxReader(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public void processOnMessages(String folder, LocalDate since, LocalDate until, Consumer<Message> mailconsumer) {
try {
System.out.println("Password:" + password);
Properties prop = new Properties();
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
prop.put("mail.debug.auth", "true");
prop.put("mail.imap.sasl.enable", "true");
prop.put("mail.imap.sasl.mechanisms", "XOAUTH2");
prop.put("mail.imap.auth.login.disable", "true");
prop.put("mail.imap.auth.plain.disable", "true");
prop.put("mail.imap.ssl.enable", "true");
// Create the session
//Connect to the server
Session session = Session.getDefaultInstance(prop, null);
session.setDebug(true);
Store store = session.getStore("imap");
store.connect(host, username, password);
//open the inbox folder
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_ONLY);
Message[] messages;
if (since != null) {
Date startDate = Date.from(since.atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm newerThan = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
SearchTerm both = new AndTerm(olderThan, newerThan);
messages = inbox.search(both);
} else {
messages = inbox.search(newerThan);
}
} else if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
messages = inbox.search(olderThan);
} else {
messages = inbox.getMessages();
}
for (Message m: messages) {
mailconsumer.accept(m);
}
inbox.close(false);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
The above statement fails at the store.connect statement with AUTHENTICATE FAILED.
I probably pass on the token incorrectly? The JavaMail documentation above states that I should not Base64 encode the token, but I received it as such. Am I supposed to send only part of it? Which part then?
Any help would be appreciated.
With a tip from a co-worker without a stackoverflow account, I finally got it to work. The key was that the scopes I used for the OAuth token, are apparantly not allowed for an application. This bit of information is hidden at the bottom of this page.
Summarized, the solution is:
You must configure the IMAP.AccessAsApp permission, instead of IMAP.AccessAsUser.All . This permission can not be found in the same place as the AccessAsUser.All permission, but is hidden under "Office 365 Exchange Online" permissions.
Unlike what you would expect, you must use the https://outlook.office365.com/.default scope in the body payload for the access token request.
That did the trick. It is ridiculous how much difficulty I had to find that information in the documentation pages using search engines.
Please check if you have enabled Allow public client flows setting in azure app registrations. This is required for ROPC as description says.
Allow public client flows
This is under App Registrations -> Overview-> redirect URIs.
I had a similar situation as the OP. I had a Java (swing) standalone app that reads and processes specific messages from the inbox. The app has been running for a couple of years using basic auth. Recently (Early October), Microsoft disable basic auth. I had to scramble to figure out OAUTH. This post got me very close; HOWEVER, the final answer did not work for me.
Getting the access token was easy enough, but authentication ALWAYS failed. Obviously, the permissions/grants for my token were not correct.
My solution:
Configure Microsoft Graph permissions IMAP.AccessAsUser.All . (opposite of OP's solution)
Scope - "email openid https://outlook.office.com/IMAP.AccessAsUser.All"
Everything else pretty much exactly followed OP.
Hope this might help another lost soul.
We are using Microsoft Graph (beta) Webhooks to get notified about presence changes in Microsoft Teams and have currently an issue at our customer.
When we receive the presence change notification from the Graph API it does not contain the validationTokens property, thus the validation and subsequent processing fails.
Our code is similar to the sample provided by Microsoft.
The (simplified/shortened) content of the received request at the customer looks like the following:
{
"value": [
{
"subscriptionId": "...",
"clientState": "...",
"changeType": "updated",
"resource": "communications/presences?$filter=id+in+(...)",
"subscriptionExpirationDateTime": "2021-04-22T02:06:56.2872368-07:00",
"resourceData": {
"#odata.id": "communications/presences?$filter=id+in+(...)",
"#odata.type": "#Microsoft.Graph.presence",
"id": "..."
},
"tenantId": "...",
"encryptedContent": {
"data": "...",
"dataSignature": "...",
"dataKey": "...",
"encryptionCertificateId": "3",
"encryptionCertificateThumbprint": "..."
}
}
]
}
Compared to our lab the request body is missing the validationTokens property:
{
"value": [
{
"subscriptionId": "...",
"clientState": "...",
"changeType": "updated",
"resource": "communications/presences?$filter=id+in+(...)",
"subscriptionExpirationDateTime": "2021-04-26T00:07:08.9251516-07:00",
"resourceData": {
"#odata.id": "communications/presences?$filter=id+in+(...)",
"#odata.type": "#Microsoft.Graph.presence",
"id": "..."
},
"tenantId": "...",
"encryptedContent": {
"data": "...",
"dataSignature": "...",
"dataKey": "...",
"encryptionCertificateId": "3",
"encryptionCertificateThumbprint": "..."
}
}
],
"validationTokens": [
"..."
]
}
According to the doc, validationTokens are only provided for change notifications with resource data - which is the case here, so we guess the validationTokens should be present?
Any hints are welcome.
Edit
Here is a shortened code snipped used to deserialize the request body/handle the notification request:
<HttpPost("/Notification/{connectorId}/{apiLinkId}")>
Public Async Function Listen(connectorId As Guid, apiLinkId As Guid, <FromQuery> Optional validationToken As String = Nothing) As Task(Of IActionResult)
If Not String.IsNullOrEmpty(validationToken) Then
' Validate the new subscription by sending the token back to Microsoft Graph.
' This response is required for each subscription.
Return Content(WebUtility.HtmlEncode(validationToken))
End If
Try
' Parse the received notifications.
Dim options As New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True}
options.Converters.Add(New JsonStringEnumConverter(JsonNamingPolicy.CamelCase))
Dim plainNotifications As New Dictionary(Of String, ChangeNotification)()
Dim notificationCollection = Await JsonSerializer.DeserializeAsync(Of ChangeNotificationCollection)(Request.Body, options)
notificationCollection.Value _
.Where(Function(x) x.EncryptedContent Is Nothing) _
.ForEach(Sub(notification)
Dim subscription = Stores.TeamsPresenceSubscriptionStore.Instance.GetValueOrDefault(notification.SubscriptionId.Value)
' Verify the current client state matches the one that was sent.
If subscription Is Nothing OrElse notification.ClientState <> subscription.SecretClientState Then
Log.msg(Category.TEAMS, "Error: Failed to verify notification")
Return
End If
' Just keep the latest notification for each resource. No point pulling data more than once.
plainNotifications(notification.Resource) = notification
End Sub)
If plainNotifications.Count > 0 Then
' Query for the changed messages
GetChangedMessages(plainNotifications.Values)
End If
If notificationCollection.ValidationTokens IsNot Nothing AndAlso notificationCollection.ValidationTokens.Any() Then
' -> notificationCollection.ValidationTokens is not set at the customer
End If
Catch ex As Exception
' Still return a 202 so the service doesn't resend the notification.
End Try
Return Accepted()
End Function
The code to create the subscription is
Subscription = graphApi.Client.Subscriptions.Request().AddAsync(New Subscription() With
{
.Resource = $"/communications/presences?$filter=id in ({String.Join(",", userIds.Select(Function(id) $"'{id}'"))})",
.ChangeType = "updated",
.NotificationUrl = $"{publicNotificationEndpoint}/Notification/{connectorid}/{Me.GraphApi.Link.Id}",
.LifecycleNotificationUrl = $"{publicNotificationEndpoint}/LifecycleNotification/{connectorid}/{Me.GraphApi.Link.Id}",
.ClientState = SecretClientState,
.ExpirationDateTime = DateTime.UtcNow.Add(MAX_SUBSCRIPTION_LIFETIME),
.EncryptionCertificate = Convert.ToBase64String(encryptionCertificate.Export(X509ContentType.Cert)),
.EncryptionCertificateId = encryptionCertificate.Version.ToString(),
.IncludeResourceData = True
}).Result
I think this is what you are looking for subscribing to the presence API using the Change Notification API, We have developed samples in csharp and node js which have the capability to notify user when presence is updated, You can take a look at following github sample code repo graph-change-notification for your scenario.
Its kind of a late reply but the validationToken will only send to the webhook at the time of Subscription creation, after that Microsoft start sending the chang notifications and there won't be any validationToken send with the change notification. This is done just to ensure that the Notification endpoint is valid/active.
Notification endpoint validation
SS taken from: https://learn.microsoft.com/en-us/graph/webhooks
I have set up firebase authentication and am trying to send a welcome email
What I do not understand is why the authenticated user does not have an email field and only has this in the provider data. All the samples use something similar to:
exports.sendWelcomeEmail = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
const email = user.email; // The email of the user.
const displayName = user.displayName; // The display name of the user.
console.log("New User created: " + JSON.stringify(user));
console.log('email:', email);
However, my authenticated user does not seem to me to have an email
My log shows
email: undefined
I am using google authentication from IOS with GIDSignInButton
My console log has the following
{
"displayName": "Ryan H",
"metadata": {
"createdAt": "2017-06-19T10:06:21.000Z",
"lastSignedInAt": "2017-06-19T10:06:21.000Z"
},
"photoURL": "https://lh6.googleusercontent.com/-MXne-lIR8e8/AAAAAAAAAAI/AAAAAAAAbeQ/Z1OvxasY/s96-c/photo.jpg",
"providerData": [
{
"displayName": "Ryan H",
"email": "ryanh#gmail.com",
"photoURL": "https://lh6.googleusercontent.com/-MXne-lIR8e8/AAAAAAAAAAI/AAAAAAAAbeQ/Z1asY/s96-c/photo.jpg",
"providerId": "google.com",
"uid": "1077081708"
}
],
"uid": "WjdlLc3QNvrmkj0yOuqo2"
}
As you can see there is no email except in the provider data.
Has there been a change in the model?
Should I always try to get my email address from the provider data?
Why are all the same code and examples I find using user.email ?
I currently have google and facebook enabled as "sign in methods".
Perhaps if I also enabled email/password I would have access.
I can confirm that in my IOS app the User after sign in does not have email either.
According to https://firebase.google.com/docs/auth/users
The first time a user signs up to your app, the user's profile data is populated using the available information:
If the user signed up with an email address and password, only the primary email address property is populated
If the user signed up with a federated identity provider, such as Google or Facebook, the account information made available by the provider is used to populate the Firebase User's profile
This is a workaround to my issue not really an answer:
I extract an email from the first provider I find, which in my case is always
Google
exports.sendWelcomeEmail = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
var email = user.email; // The email of the user.
if (email == undefined) {
for (var provider of user.providerData) {
if (provider.email) {
email = provider.email;
break;
}
}
}
const displayName = user.displayName; // The display name of the user.
// [END eventAttributes]
console.log("New User created: " + JSON.stringify(user));
console.log('email:', email);
console.log('displayName:', displayName);
sendWelcomeEmail(email,displayName)
return;
});`
I am new with grails and am developing a web application in grails.
In my registration page I am getting the user's email id and I need to send a mail with authentication link.
http://grails.org/plugin/mail
http://grails.org/plugin/email-confirmation
I have referred these pages and many other pages to do this task.
But the problem is, my email is not sending.
I have used
Gmail SMTP server address : smtp.gmail.com
Gmail SMTP username : myid#gmail.com
Gmail SMTP password : -my password-
Gmail SMTP port : 465
Gmail SMTP TLS/SSL required : yes
Mail settings are:
grails {
mail {
host = "smtp.gmail.com"
port = 465
username = "myId#gmail.com"
password = "mypassword"
props = [
"mail.smtp.auth":"true",
"mail.smtp.socketFactory.port":"465",
"mail.smtp.socketFactory.class": "javax.net.ssl.SSLSocketFactory",
"mail.smtp.socketFactory.fallback":"false"]
}
}
grails.mail.default.from="noreply#gmail.com"
but at least
sendMail {
to "friend#gmail.com"
subject "Hello "
body 'How are you?'
}
is not working.
The exception occured is
Error 500: Internal Server Error
URI
/MailVerificationDemo/user/signup/form
Class
java.net.ConnectException
Message
Connection refused
Around line 104 of MailMessageBuilder.groovy
101: log.trace("Sending mail ${getDescription(message)}} ...")102: }103:104: mailSender.send(message instanceof MimeMailMessage ? message.mimeMessage : message)105:106: if (log.traceEnabled) {107: log.trace("Sent mail ${getDescription(message)}} ...")
Around line 41 of grails-app/services/grails/plugin/mail/MailService.groovy
38: callable.resolveStrategy = Closure.DELEGATE_FIRST39: callable.call()40:41: messageBuilder.sendMessage()42: }43:44: def getMailConfig() {
Around line 18 of grails-app/controllers/user/UserController.groovy
15: return16: }17:18: mailService.sendMail {19: to userInstance.email20: subject "New User Confirmation"21: html g.render(template:"mailtemplate",model:[code:userInstance.confirmCode])
Around line 195 of PageFragmentCachingFilter.java
192: if (CollectionUtils.isEmpty(cacheOperations)) {193: log.debug("No cacheable annotation found for {}:{} {}",194: new Object[] { request.getMethod(), request.getRequestURI(), getContext() });195: chain.doFilter(request, response);196: return;197: }198:
Around line 63 of AbstractFilter.java
60: try {61: // NO_FILTER set for RequestDispatcher forwards to avoid double gzipping62: if (filterNotDisabled(request)) {63: doFilter(request, response, chain);64: }65: else {66: chain.doFilter(req, res);
Try this it's worked for me.
Notice that: Gmail SMTP TLS/SSL required : yes.
But you don't put "mail.smtp.starttls.enable": "true"
grails.mail.host="smtp.gmail.com"
grails.mail.port=587
grails.mail.username="yourUsernameHere"
grails.mail.password="yourPwdHere"
grails.mail.from="defaultMailFromHere"
grails.mail.props = ['mail.smtp.auth': "true",
"mail.smtp.starttls.enable": "true",
"mail.from":"defaultMailFromHere"]
grails.mail.javaMailProperties = ['mail.smtp.auth': "true",
"mail.smtp.starttls.enable": "true",
"mail.from":"defaultMailFromHere"]
I am using the Docusign Rest API, through a gem for Rails. I have 2 recipients, and need both of them to sign the document.
It should work like this:
Generate envelope with borrower(s) info passing to the envelope.
Display embedded document for signing.
Return to custom url/action
If there is another signer, it should
reload the iframe
ask for second signers signature, with the same template that was just signed
Instead it breaks when I reloads the iframe for the 2nd signer. It generates the envelope, with my second signer with its unique ID, email etc. However when I then create the recipient view, it returns nil.
How do I get signer 1 to sign, then load it for the second signer right after, with all the custom fields filled both times?
def deliver(signing_borrower)
client = DocusignRest::Client.new
hashData = {
status: 'sent',
template_id: my_id
signers: [
{
embedded: true,
name: signing_borrower.name,
email: signing_borrower_email,
role_name: if (signing_borrower==borrower) then 'Borrower' else 'Co-borrower' end
}
]
}
generate_liabilities
hashData[:signers][0][:tabs] = if (signing_borrower==borrower) then custom_fields else co_borrower_custom_fields end
#if there is a coborrower, add that data to the form as the second signer
if co_borrower
if signing_borrower==co_borrower then opposite_of_signing_borrower = borrower else opposite_of_signing_borrower = co_borrower end
borrower2= {
name: opposite_of_signing_borrower.name,
email: signing_borrower_email(opposite_of_signing_borrower),
role_name: if (opposite_of_signing_borrower==co_borrower) then 'Co-borrower' else 'Borrower' end
}
#add second borrower to hash to generate envelope with all form fields filled
hashData[:signers] << borrower2
hashData[:signers][1][:tabs] = {
textTabs: text_tabs,
checkboxTabs: checkbox_tabs
}
end
response = client.create_envelope_from_template hashData
self.envelope_id = response["envelopeId"]
self.signing_url = DocusignRest::Client.new.get_recipient_view({
envelope_id: self.envelope_id,
name: signing_borrower.name,
email: signing_borrower_email(signing_borrower),
return_url: return_url
})
response
end
The hashData
{:status=>"sent",
:email=>
{:subject=>"Application...",
:body=>"please sign...."},
:template_id=>"XXXX-XXXX-XXXX",
:signers=>
[{:embedded=>true,
:name=>"DAVID L. TESTCASE",
:email=>"email#test.com",
:role_name=>"Borrower",
:tabs=>
{:textTabs=>
[{:tablabel=>"phone",
:name=>"phone",
:value=>"717-717-7171"}]
}
},
{:name=>"MARISOL TESTCASE",
:email=>"email2#test.com",
:role_name=>"Co-borrower",
:tabs=>
{:textTabs=>
[{:tablabel=>"phone",
:name=>"phone",
:value=>"717-717-7171"}]
}
}]}
You can accomplish what you're trying to do by making 3 API calls:
Create Envelope request, specifying both recipients as 'embedded/captive' by setting clientUserId property for each recipient.
POST Recipient View request to get the URL to launch the first signer's signing session.
POST Recipient View request to get the URL to launch the second signer's signing session.
Here's example JSON for those three calls.
1 - Create Envelope Request
POST https://{{env}}.docusign.net/restapi/{{version}}/accounts/{{acctId}}/envelopes
{
"emailSubject": "Please sign this",
"emailBlurb": "Please sign...thanks!",
"templateId": "064A7973-B7C1-41F3-A2AD-923CE8889333",
"status": "sent",
"templateRoles": [
{
"roleName": "Borrower",
"name": "John Doe",
"email": "johnsemail#outlook.com",
"recipientId": "1",
"clientUserId": "123",
"tabs":{
"textTabs":[
{
"tabLabel":"BorrowerPhone",
"value":"717-717-7171"
},
],
}
},
{
"roleName": "Co-borrower",
"name": "Jane Doe",
"email": "janesemail#outlook.com",
"recipientId": "2",
"clientUserId": "567",
"tabs":{
"textTabs":[
{
"tabLabel":"Co-borrowerPhone",
"value":"717-717-7171"
},
],
}
}
]
}
A successful response will contain the Id of the newly created Envelope.
2 - POST Recipient View (for the First Recipient)
Make this call when the first signer is ready to sign. In the request URL, {{envelopeId}} is the Envelope Id value that was returned in the response of the Create Envelope request, and information in the request body corresponds to info you submitted for the first recipient in the Create Envelope request.
POST https://{{env}}.docusign.net/restapi/{{version}}/accounts/{{acctId}}/envelopes/{{envelopeId}}/views/recipient
{
"authenticationMethod": "Email",
"clientUserId": "123",
"userName": "John Doe",
"email": "johnsemail#outlook.com",
"returnUrl": "http://www.google.com"
}
The response will contain the URL that can be used to launch the DocuSign Envelope for the first recipient.
3 - POST Recipient View (for the Second Recipient)
Make this call when it's time for the second signer to sign. In the request URL, {{envelopeId}} is the Envelope Id value that was returned in the response of the Create Envelope request, and information in the request corresponds to info you submitted for the second recipient in the Create Envelope request.
POST https://{{env}}.docusign.net/restapi/{{version}}/accounts/{{acctId}}/envelopes/{{envelopeId}}/views/recipient
{
"authenticationMethod": "Email",
"clientUserId": "567",
"userName": "Jane Doe",
"email": "janesemail#outlook.com",
"returnUrl": "http://www.google.com"
}
The response will contain the URL that can be used to launch the DocuSign Envelope for the second recipient.
I think the problem is in the JSON request body that you're building. I'm curious where you added the embedded => true property from, that's not in the API docs or examples. As far as I know that is not a valid property, but I think the DocuSign API is forgiving in that it doesn't error out when non-recognized properties are sent, it ignores them instead.
In general, when you are creating Embedded (aka "Captive") recipients you need to configure at least 3 properties for these recipients:
1. email
2. name
3. clientUserId
All of these are client configurable (i.e. you can set them to whatever you want), however whatever values you use when adding each recipient.. you need to reference the same exact values when requesting a signing URL for them.
For example, to add two embedded recipients you can send the following (partial) request body:
"signers": [
{
"name": "Sally Doe",
"email": "test_1#email.com"
"clientUserId": "1000"
},
{
"name": "John Doe",
"email": "test_2#email.com"
"clientUserId": "1001"
}
]
If you do not specify the routingOrder for your recipients then it will default to routing order = 1. And if you don't specify a recipientId, the system will generate a unique GUID for each and assign to them.
Once your envelope has been created and your recipients have been added to it, you can then request the signing URLs, but as mentioned you'll need to reference the same values for name, email, and clientUserId.
For more info you can check out the page on Embedding functionality at the DocuSign Developer Center:
http://www.docusign.com/developer-center/explore/features