connect from web to iot core using a custom authorizer - iot

I'm trying to use a custom authorizer to authenticate a web client.
I have succesfully created a dedicated lambda and a custom authorizer. If I launch aws iot describe-authorizer --authorizer-name <authorizer-name> I can see
{
"authorizerDescription": {
"authorizerName": "<authorizer-name>",
"authorizerArn": "...",
"authorizerFunctionArn": "...",
"tokenKeyName": "<token-key-name>",
"tokenSigningPublicKeys": {
"<public-key-name>": "-----BEGIN PUBLIC KEY-----\n<public-key-content>\n-----END PUBLIC KEY-----"
},
"status": "ACTIVE",
"creationDate": "...",
"lastModifiedDate": "...",
"signingDisabled": false,
"enableCachingForHttp": false
}
}
Moreover I can test it succesfully:
$ aws iot test-invoke-authorizer --authorizer-name '<authorizer-name>' --token '<public-key-name>' --token-signature '<private-key-content>'
{
"isAuthenticated": true,
"principalId": "...",
"policyDocuments": [ "..." ],
"refreshAfterInSeconds": 600,
"disconnectAfterInSeconds": 3600
}
$
But I cannot connect using the browser.
I'm using aws-iot-device-sdk and according the SDK documentation I should set customAuthHeaders and/or customAuthQueryString (my understanding is that the latter should be used in web environment due to a limitation of the browsers) with the headers / queryparams X-Amz-CustomAuthorizer-Name, X-Amz-CustomAuthorizer-Signature and TestAuthorizerToken but no matter what combination I set for these values the iot endpoint always close the connection (I see a 1000 / 1005 code for the closed connection)
What I've written so far is
const CUSTOM_AUTHORIZER_NAME = '<authorizer-name>';
const CUSTOM_AUTHORIZER_SIGNATURE = '<private-key-content>';
const TOKEN_KEY_NAME = 'TestAuthorizerToken';
const TEST_AUTHORIZER_TOKEN = '<public-key-name>';
function f(k: string, v?: string, p: string = '&'): string {
if (!v)
return '';
return `${p}${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
}
const client = new device({
region: '...',
clientId: '...',
protocol: 'wss-custom-auth' as any,
host: '...',
debug: true,
// customAuthHeaders: {
// 'X-Amz-CustomAuthorizer-Name': CUSTOM_AUTHORIZER_NAME,
// 'X-Amz-CustomAuthorizer-Signature': CUSTOM_AUTHORIZER_SIGNATURE,
// [TOKEN_KEY_NAME]: TEST_AUTHORIZER_TOKEN
// },
customAuthQueryString: `${f('X-Amz-CustomAuthorizer-Name', CUSTOM_AUTHORIZER_NAME, '?')}${f('X-Amz-CustomAuthorizer-Signature', CUSTOM_AUTHORIZER_SIGNATURE)}${f(TOKEN_KEY_NAME, TEST_AUTHORIZER_TOKEN)}`,
} as any);
As you can see I started having also doubts about the headers names!
After running my code I see that the client tries to do a GET to the host with the querystring that I wrote.
I also see that IoT core responds with a 101 Switching Protocols, and then that my client send the CONNECT command to IoT via websocket and then another packet from my browser to the backend system.
Then the connection is closed by IoT.
Looking at cloudwatch I cannot see any interaction with the lambda, it's like the request is blocked.
my doubts are:
first of all, is it possible to connect via mqtt+wss using only a custom auth, without cognito/certificates? keep in mind that I am able to use a cognito identity pool without errors, but I need to remove it.
is it correct that I just need to set up the customAuthQueryString parameter? my understanding is that this should be used on the web.
what are the values I should set up for the various headers/queryparams? X-Amz-CustomAuthorizer-Name is self explanatory, but I'm not sure about X-Amz-CustomAuthorizer-Signature (it's correct to fill it with the content of my private key?). moreover I'm not sure about the TestAuthorizerToken. Is it the correct key to set up?
I've also tried to run the custom_authorizer_connect of the sdk v2 but it's still not working, and I run out of ideas.

turns out the problem was in the permissions set on the backend systems.

Related

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3, but can't fetch token using (client_credentials) grant type

Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3
npm install #aws-sdk/client-cognito-identity-provider.
The following code shows how I created the resources server and the user pool client, using the mentioned👆 SDK...
let poolName = 'UserPool';
const client =new CognitoIdentityProviderClient({
region: process.env.COGNITO_AWS_REGION
});
// create resource server
const createResourceServerCommand = new CreateResourceServerCommand({
Name: poolName,
UserPoolId: UserPool.Id,
Identifier: 'https://localhost:8080/api/v2',
Scopes: [
{
ScopeName: 'access',
ScopeDescription: 'General access to API'
}
]
});
const { ResourceServer } = await client.send(createResourceServerCommand);
// create the user pool client
const createUserPoolClientCommand = new CreateUserPoolClientCommand({
ClientName: 'Default',
UserPoolId: UserPool.Id,
ExplicitAuthFlows: ['USER_PASSWORD_AUTH'],
GenerateSecret: true,
AllowedOAuthFlows: ['client_credentials'],
SupportedIdentityProviders: ['COGNITO'],
AllowedOAuthScopes: [ 'https://localhost:8080/api/v2/access' ]
});
const { UserPoolClient } = await client.send(createUserPoolClientCommand);
...but, I can't fetch tokens using the grant type client_credentials. Therefore getting the following error.
{
"error": "invalid_grant"
}
However, if I use AWS console to navigate to the user pool > Client > Edit the hosted UI and click on the save button without making any changes...
... I am able to fetch a token using the client_credentials grant type.
Is there any setting that I might be missing in the above code that AWS console is setting? I need the following code to automate the creation of user pools.
When I switched to the old I noticed this notification
Apparently, Oauth flows are not enabled by default. Hence adding the following attribute to the CreateUserPoolClientCommandInput object AllowedOAuthFlowsUserPoolClient: true enables it. Hope this helps some newbie like me out there.

Unable to connect to Firebase Emulator with iOS device

I am trying to connect with an iOS device to the Firebase Auth and RealTime Database Emulator.
The thing is, I can connect and use emulator through Firebase Admin using NodeJS on local machine (trough http://localhost:9000?ns=my-project).
Also I am able to connect with an iOS device to the remote Firebase server... But locally it doesn't work. It throws bunch of random errors, like this (when I try to complete registration/authentication):
Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the
server." NSLocalizedDescription=Could not connect to the server.,
NSErrorFailingURLStringKey=http://192.168.1.3:9099/www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=myKeyGoesHere
and
Optional(Error Domain=com.firebase.core Code=1 "Unable to get latest
value for query FQuerySpec (path: /news, params: { }), client offline
with no active listeners and no matching disk cache entries"
Here is firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"emulators": {
"auth": {
"port": 9099
},
"database": {
"port": 9000
},
"ui": {
"enabled": true
}
}
}
I changed rules just in case:
{
"rules": {
".read": true,
".write": true
}
}
but its not that.
and here is how I try to connect to database in my iOS application(my FirebaseManager class):
init(){
Auth.auth().useEmulator(withHost:"192.168.1.3", port:9099)
}
private lazy var newsNodeRef:DatabaseReference? = {
guard let urlString = getBaseURL() else {return nil}
let node = LocalConstants.kNewsRef // this has value of 'news'
return Database.database(url: urlString).reference(withPath: node)
}()
private func getBaseURL()->String?{
let environment = Environment()
guard let connectionProtocol = environment.configuration(PlistKey.firebaseConnectionProtocol), let baseURL = environment.configuration(PlistKey.firebaseDatabaseURL) else {return nil}
let urlString = "\(connectionProtocol)://\(baseURL)"
return urlString // this produces something like 'http://192.168.1.3:9000?ns=my-project' (its fetched from Configuration Settings file based on selected environment)
}
the thing is, the exact same setup works on remote server, if I just change the environment(which automatically changes base url).
I have also allowed insecure http loads in info.plist, just to be sure if it is not that, but still doesn't work.
This is what I get in console when I run emulators:
What is the problem here?
I replied a little late 😊.
I saw the solution you found. It didn't work for me but I'm sure it has worked for a lot of people.
I found a solution too.
Actually, I couldn't see a problem for iOS 15. My problem was that it didn't work on iOS 14 and earlier.
Solution;
First, you need the MacBook's IP address.
To find the IP address;
You can access it right under System preferences -> Network -> Status.
Then we need to make some changes in the firebase.json file.
Adding “host” : “IP” for each part.
Overwrite the “host” part with the “port” part.
"emulators": {
"auth": {
"host": "192.168.1.11”,
"port": 9100
},
"functions": {
"host": "192.168.1.11”,
"port": 5002
},
"firestore": {
"host": "192.168.1.11”,
"port": 8081
},
"database": {
"host": "192.168.1.11",
"port": 9001
},
"storage": {
"host": "192.168.1.11",
"port": 9200
},
"ui": {
"enabled": true
}
Then we need to add in swift codes.
We need to write the IP address in the host part.
More precisely, we will replace the parts that say localhost with the IP address.
let settings = Firestore.firestore().settings
settings.host = "192.168.1.11:8081"
settings.isPersistenceEnabled = false
settings.isSSLEnabled = false
Firestore.firestore().settings = settings
Storage.storage().useEmulator(withHost:"192.168.1.11", port:9200)
Auth.auth().useEmulator(withHost:"192.168.1.11", port:9100)
let db = Database.database(url:"http://192.168.1.11:9001?ns=firebaseappname")
Functions.functions().useFunctionsEmulator(origin: "http://192.168.1.11:5002")
I think this solution will work in JS, android and other languages.
I would appreciate it if you tried this solution and let me know if it works.
It worked for me.
I actually solved it. The solution/problem, I don't even know how to declare it, was with Local Network Access prompt & permissions and its buggy behaviour (as well how I was trying to access my Mac by ip).
At first I didn't even see a prompt shows every time, but I guess it was related to a wrong setup of a port, host etc.
But when I correctly set local computer's ip and reverted firebase.json to it's default settings (which is what worked for me), the prompt started to jump out every time.
The thing is, prompt's behaviour seems broken, because instead of jumping before you try to access devices in a local network, it pops out after that action is made. Quite fast, but still after Auth system responded, which doesn't make sense.
Here, it can be confusing, cause error that is returned from a Firebase Auth system in the case when you didn't allow Local Network Access usage, doesn't really tell you much about real cause. See my ( original question) above to see the errors.
After that terrible flow, I allowed access trough the prompt. Once I did that, on every next 'api' call towards Emulator was successful. Worked like a charm.
The real problem here is Local Network Access prompt. Cause we don't have at all control over it, so we can't that easily trigger it, or easily get info what user have selected/chosen at the moment / or before. It's triggered by the system in certain conditions.
Luckily this is just for development :) but I hope it will be fixed/improved soon, cause it should.
I found a lot about this topic and its considered as a bug Local Network Access Prompt problems on Dev portal:
I was also faced the same problem while using the firebase auth in iOS simulator
then i change my code little bit
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
if (Platform.isAndroid) {
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
}
runApp(const MyApp());
}
I have made a check for only android and it works for me
Inside this method you can see it only works for android.

Issue authenticating to Auth0 via Swagger UI (.NET) with client credentials

I'm trying to authenticate to my Auth0 tenant via the Swagger UI (auto-generated by Swashbuckle.AspNetCore) using client credentials.
I'm getting the following error:
Auth ErrorError, error: access_denied, description: Non-global clients
are not allowed access to APIv1
Here's a screen shot:
Open API spec looks like this:
"securitySchemes": {
"auth0": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "https://example.auth0.com/oauth/token"
},
"authorizationCode": {
"authorizationUrl": "https://example.auth0.com/authorize?audience=test",
"tokenUrl": "https://example.auth0.com/oauth/token",
"scopes": { }
}
}
}
}
I suspect this has something to do with the audience not being specified. I was having a similar issue with authorization code flow, but manged to get this working by appending the audience as a query-string param to the authorizationUrl (as shown above). Unfortunately the same trick doesn't work with client credential flow (i.e. attempting to append the audience to the tokenUrl). I need to support client credential flow, as one or more of the routes need to be locked down to M2M tokens only.
Interestingly, it works if I use the global client ID / secret (found under the advanced settings of the tenant), but I'm not sure if we should use this...
Anyone else ran into this issue and if so, any luck with finding a solution?

jwt authentication in iOS client nodejs server via third party authenticator

I am trying to wrap my head around using json webtoken (jwt) based authentication on a server coupled to using a third party (say google) to authenticate the user. Originally I've managed to build my own login and jwt handling scheme with jsonwebtoken on my nodejs server, but we need a client running on an iOS system to interact with it and started looking around for a solution where we don't have to code so much client code (requesting new token when expired etc.) and thought that we would use a third party library to do this for us.
The thing is I did not find anything that would do this for us. I found libraries that could handle connecting the client to a google api for the client, I found user identification handled by google, but didn't find anything that would handle actually getting a jwt that the server would except as a genuine user.
My question is essentially this: we have an iOS client and a nodejs server and would like to use google to authenticate our users and have the client call api-s on our nodejs server, with as much of the authentication process handled by some third party library (google's?), how should we get around to this?
As a note, I've seen passport but that seems to operate with sessions only, and I would have to solve the jwt handling by myself were I to use that.
The iOS part is not ready, but I managed to use google to authenticate and authorize without a session in the browser. The idea is, that the client logs in to google (see here for web app) and google graciously also gives you a token with the login, which will be good for the server. On the nodejs side I used passport and the google-id-token strategy (see on github). There are quite a few strategies for google out there, but this one works. Although, this has a shortcoming, it can't accept the token in the header, but I fixed that in a pull request (see here).
Since I had a bit of a problem of how to use the User.findOrCreate part of all the passport examples, I'll put in my code here that covers a full working example:
var passport = require('passport');
var GoogleTokenStrategy = require(passport-google-id-token)
passport.use(new GoogleTokenStrategy({
clientID: config.googleAuth.clientID,
clientSecret: config.googleAuth.clientSecret,
},
function(parsedToken, googleId, done) {
console.log(parsedToken);
console.log(googleId);
User.findOne({ 'google.id': googleId }, function (err, user) {
if (!user) {
var testuser = new User({
name: parsedToken.payload.name,
givenName : parsedToken.payload.givenName,
familyName : parsedToken.payload.familyName,
nameunderscore : parsedToken.payload.name.split(' ').join("_"),
admin: false,
email: parsedToken.payload.email,
settings: {save_folder:"default"},
'google.id' : googleId,
'google.email' : parsedToken.payload.email,
});
testuser.save(function(err) {})
}
return done(err, user);
});
}
));
User comes from mongodb in a separate js:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({
name: String,
nameunderscore : String,
givenName: String,
familyName: String,
admin: Boolean,
settings: {
save_folder: String
},
email: String,
google: {
id: String,
email: String
}
}));
And this is how I added the passport strategy to a router (note that session is set to false):
var apiRoutes = express.Router();
apiRoutes.use(passport.authenticate('google-id-token',{ session: false }));
Now every call to any route in apiRoutes must send on id_token with a valid google token to get access.

Error in Push Notifications while registering for iOS Twilio Voice Client

I am getting the following error while trying to register a user for the Twilio Voice client:
[ERROR VoiceClient] Inside register:deviceToken:completion:, failed to register for Twilio push notifications. Error:Failed to register. Code: 6.
Here is the setup:
Push credentials have been registered with Twilio. A VoIP Push certificate has been registered and those kind of pushes are only in Production mode. Let's say the app ID is "com.bundle.appIDX".
The server side has been setup so that the correct push credentials are fed to the IpMessagingGrant object.
At the client side, the following piece of code is executed:
VoiceClient.sharedInstance().register(withAccessToken: self.accessToken, deviceToken: self.voipToken)
This immediately fails with the error above.
Using version '=2.0.0-beta4' for TwilioVoiceClient, and using version '2.9.1' for twilio-node server side component (https://github.com/twilio/twilio-node/tree/2.9.1).
Tried playing with "Use this credential for sending to a sandbox APN" option and no result.
Tried using both development and provisioning profiles at the client side for the app ID "com.bundle.appIDX".
Is Twilio really supporting VoIP pushes? If yes, what could be wrong with this setup?
Thanks,
Guven.
=======
Edit after Viktor's guidance:
I now manually create the VoiceGrant. I set the value of the key property to 'voice'. Here is what the grant looks like:
{
outgoing_application_sid: 'APXX',
push_credential_sid: 'CRXX',
endpoint_id: 'XX'
}
Still getting the registration error.
I have also tried this format since this is how it looks in 2.11.0 version:
{ outgoing: { application_sid: 'APXX' },
push_credential_sid: 'CRXX',
endpoint_id: 'XX'
}
Any ideas where the problem might be? Attaching the cloud code as well:
var accessToken = new twilio.AccessToken("ACXX", "SKXX", "PPXX", accessTokenOptions);
var voiceGrantConfig = {"outgoingApplicationSid": "APXX",
"endpointId": clientName,
"pushCredentialSid": "CRXX"};
var voiceGrant = new VoiceGrant(voiceGrantConfig);
voiceGrant.key = "voice";
console.log(voiceGrant.toPayload());
accessToken.addGrant(voiceGrant);
var token = accessToken.toJwt();
Edit 2: I have actually upgraded to twilio-node 2.11.0 and still getting the error. Here is the access token right before generating the jwt.
AccessToken {
accountSid: 'ACXX',
keySid: 'SKXX',
secret: 'PPXX',
ttl: 86400,
identity: undefined,
nbf: undefined,
grants:
[ VoiceGrant {
outgoingApplicationSid: 'APXX',
outgoingApplicationParams: undefined,
pushCredentialSid: 'CRXX',
endpointId: 'XX' } ] }
[Edited based on feedback from Guven]
You need a "voice" grant. Otherwise your requests will be denied to the Voice product. You either need to manually add that section to the JSON before signing it or you need to upgrade to the 2.11.0 version. Specifically your token needs to look like this:
{
"sub": "ACxxxx",
"iss": "SKxxxx",
"grants": {
"voice": {
"outgoing": {
"application_sid": "APxxxx"
},
"push_credential_sid": "CRxxxxx"
},
"identity": "voice_test"
},
"jti": "SKxxxxx",
"exp": 1479315711
}
Note, also the "identity" element as well. This is the identifier of your user. Typically its username or an ID of the user record in your system.
Checkout the quickstart app for a code sample for generating the right token (it's in Python but should give you a good idea of how to get started).

Resources