Amplify datastore "Unauthorized" error on custom auth identityClaim - aws-amplify

When I change the identityClaim field in my model, I got the error ""Unauthorized","message":"Not Authorized to access..." when I try to start Datastore in the app.
But, it works as expected when I run the query in AWS AppSync.(It saves the groupID in owner field)
Here is the configuration:
type Book
#model
#auth(rules: [{ allow: owner, identityClaim: "custom:groupID" }]) {
id: ID!
name: String!
owner: Strin
}

i had this same issue and resolved it by using the graphql_headers Amplify.config option to override the default datastore Authorization header. it was using the Access token by default, but to use a custom cognito attribute as the identity claim, you need to use the ID token, because those custom attributes aren’t present on the Access token (i documented all the gory details in a GitHub issue). in my app, it looks like:
Amplify.configure({
...awsconfig,
// https://github.com/aws-amplify/amplify-cli/issues/3794
graphql_headers: async () => {
try {
const token = (await Auth.currentSession()).getIdToken().getJwtToken();
return { Authorization: token };
} catch (e) {
console.error(e);
return {};
}
},
});
note that your owner field has a typo (Strin → String), but i’m guessing that’s just a copy/paste issue.

Related

Access supabase database from edge function as admin

I'm coming from Firebase Function... Where I can use the Admin Library to access database from the Function bypassing all security rules? On supabase I didn't yet find a way to do that, the documentation is very scarce. Now I'm using this code to access the database from function as the user who requested the function:
const supabaseClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
{ global: { headers: { Authorization: req.headers.get("Authorization")! } } }
);
But for one of my function, I have to access bypassing all policies, as the function was "admin", and when I remove de third params line in this code (Which was the only vague explanation how to do that I found) I get the error:
AuthApiError: invalid claim: missing sub claim at ...
I also tried change the SUPABASE_ANON_KEY to SUPABASE_SERVICE_ROLE_KEY, same error.
Use a service role key to bypass the security rules, make sure you have a valid service key role .
The key you are using Deno.env.get("SUPABASE_ANON_KEY") ?? it has anonymous access and will not bypass security rules.
const supabaseClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
{ global: { headers: { Authorization: `Bearer ${Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")}` } } });
Replace the "SUPABASE_SERVICE_ROLE_KEY" with value of actual service role key to to bypass security rules.

How to use my firebase authentication to work with external services?

Ok so I am using firebase as authentication for my iOS app. Now I plan on adding video calling to my app using an external service know as connectyCube. This service has their own authentication system and I cannot use their services unless a user is authenticated.
Option 1: I can use their own authentication which means my app would have two authentication systems - not very productive
Option 2: They say I can use an existing authentication to validate users
I understand that this is a common thing in the developers world and I see the word OAuth and JWT being thrown around but I am a rookie developer and I want to understand how I can use firebase and authenticate a user from an external service.
These are the questions they have asked when I opted for the "I have my own authentication" option:
What is your end point URL
Is it GET or POST
Request Headers
Request Params
Response Params
Where do I get all this information from firebase? Any help would be great
As an alternative to #Dharmaraj's answer, you could instead make use of a HTTP Event Cloud Function for this based on the code sample they've provided.
Using this method, you create the endpoint /verifyUserToken to be used by ConnectyCube.
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
export const verifyUserToken = functions.https.onRequest((req, res) => {
const idToken = req.query.token;
verifyUser(idToken)
.then(
(userData) => {
res.status(200).json(userData)
},
(err) => {
console.log("Token verification failed.", err.code || err.message);
res.status(422).json({error: "User token is invalid"})
}
)
.catch((err) => console.error("Unexpected crash", err));
});
async function verifyUser(token) {
if (!token)
throw new Error("token missing");
// using `true` here to force token to be checked against the Firebase
// Auth API rather than trusting its contents as-is
const { uid, email } = await admin.auth().verifyIdToken(token, true);
// pull the user's username from their user data
// at /users/{userId}/username
const username = (await admin.database().ref("users/" + uid + "/username")).val();
// use user's actual email if available, otherwise fallback
// to a userID based email
const uEmail = email || uid + "#users.noreply.yourapp.com";
// use user's username if available, otherwise fallback to
// the email address above.
const uLogin = username !== null ? username : uEmail;
return {
uid,
login: uLogin,
email: uEmail,
user: {id: uid, login: uLogin, email: uEmail}, // <- this part in particular is used by ConnectyCube
users: [{uid, login: uLogin, email: uEmail}]
};
}
Once deployed, you would use the following settings:
Setting
Value
API URL:
https://us-central1-PROJECT-ID.cloudfunctions.net/verifyUserToken
GET/POST
GET
Request params:
{"token": "#{login}"}
Response params:
{"uid": "#{user.id}", "email": #{user.email}, "login": "#{user.login}"}
It looks like ConnectyCube uses some sort of Session Tokens as mentioned in their documentation with their own username and password.
The most easiest way would be creating a ConnectyCube account whenever a new user signs up in your Firebase app using Firebase Auth Triggers for Cloud functions. Then you can generate username and password on behalf of your user and store them in a Database.
So whenever you need to create a ConnectyCube session, check for the currently logged in user and fetch their ConnectyCube credentials.
async function createCCSession() {
const userId = firebase.auth().currentUser.uid
const ccCrednetials = (await firebase.database().ref(`ccCreds/${userId}`).once('value')).val()
ConnectyCube.createSession(ccCredentials)
.then((session) => {
console.log(session)
return session
}).catch((error) => console.log(error));
}
You can protect the database using security rules so a user can access their credentials only.
{
"rules": {
"ccCreds": {
"$uid": {
".read": "$uid === auth.uid"
}
}
}
}
While I don't normally double-answer a question, in the course of exploring some other authentication related problems, I've managed to eliminate the Cloud Function from my other answer entirely and instead call the Authentication API directly.
Setting
Value
API URL:
https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=FIREBASE_CONFIG_API_KEY
GET/POST
POST
Request params:
{"idToken": "#{login}"}
Response params:
{"uid": "#{users.0.localId}", "email": #{users.0.email}, "full_name": "#{users.0.displayName}"}
On your client, you just call the ConnectyCube Login API with the following data:
POST https://api.connectycube.com/login
login=<Firebase-ID-token>
password=<any-random-value-to-pass-the-validation>

Github OAuth using Firebase - how to get user's username

I followed the Firebase's guide on how to authenticate with Github. https://firebase.google.com/docs/auth/web/github-auth
The return result from Firebase's signInWithRedirect method contains the user's displayName and email, etc. However, it doesn't seem to contain user's 'login' username which is the key for invoking most of Github's API calls.
I am sure there is a way to get it, but I just can't seem to find any documentation. Does anyone happen to know how to solve it?
I ended up using Github's API to get user's username with accessToken.
You should be able to get the user's GitHub username through a parameter called "username" (see more here: https://github.com/firebase/firebase-simple-login/blob/master/docs/v1/providers/github.md)
Note: firebase-simple-login was deprecated on October 3th, 2014
You can use get the authenticated user from this GitHub's api
Or if you use octokit javascript rest api client, you can do something like this
octokit = new Octokit({auth: userAccessToken })
octokit.users.getAuthenticated()
.then(result => {
console.log(result.data.login) // this is the username
})
Note: you'll get accessToken after GitHub <-> firebase login
Hope this is helpful!
You can get the username in additionalUserInfo:
const githubProvider = new firebaseClient.auth.GithubAuthProvider();
githubProvider.addScope('read:user');
githubProvider.setCustomParameters({
allow_signup: false,
});
firebaseClient.initializeApp(clientConfig);
async function submit() {
try {
const response = await firebaseClient
.auth()
.signInWithPopup(githubProvider);
console.log(response.additionalUserInfo);
} catch (error) {
alert(error);
}
}
You Can use email to do authorized requests insted username:
Username: mayGitHubEmail#mail.com
Password: accessToken
like this with Postman
body sent
Here is a sample using class func in Swift using Alamofire and SwiftyJSON pods:
import Alamofire
import SwiftyJSON
enum NetworkError: Error {
case url
case server
case auth
}
class GistServices {
class func makePostApiCall(toUrl path: String, withBody parameters: JSON, usingCredentials: Bool = false) -> Result<Data?, NetworkError> {
guard let url = URL(string: path) else {
return .failure(.url)
}
if let email = UserAuthSingleton.shared.get(), let password = UserAuthSingleton.shared.getUserToken() {
var result: Result<Data?, NetworkError>!
var request = AF.request(url, method: .post, parameters: parameters)
if(usingCredentials){
let credentialData = "\(email):\(password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = [HTTPHeader(name: "Authorization", value: "Basic \(base64Credentials)"),
HTTPHeader(name: "Accept", value: "application/json"),
HTTPHeader(name: "Content-Type", value: "application/json")]
request = AF.request(url, method: .post, parameters: parameters.dictionaryValue, encoder: JSONParameterEncoder.default, headers: HTTPHeaders(headers))
}
request
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { (response) in
switch response.result {
case .failure(_):
result = .failure(.server)
case .success(let value):
result = .success(value)
}
}
return result
}
return .failure(.auth)
}
}

Meteor - Validating new user document serverside

I'm having trouble with this seemingly trivial stuff, harrrr!
I have this user document:
userData = {
account: {
type: 'free'
},
profile: {
name: 'Artem',
},
username: 'aaa#gmail.com',
password: '123'
};
Which I'm sending client-side: Accounts.createUser(userData);
Then server side I want to check if account type equals 'free'. If it doesn't - I want to abort new user creation (and hopefully throw error client side)
There are 2 functions which I've found in the docs that presumably can help me do it:
Accounts.validateNewUser
Problem: it receives 'trimmed-down' user object which doesn't contain properties other than profile, username, password, email. Thus I cannot validate account.type as it doesn't exist on user object being validated.
Accounts.onCreateUser
Problem: it is called after a generic user object is created and there is no way I can cancel inserting new document in Users collection. It absolutely requires to return a user document. If I return undefined it throws errors on server:
Exception while invoking method 'createUser' Error: insert requires an argument
It also doesn't allow to throw method errors (as it's not a method) -> thus I cannot log error client side.
You can use Accounts.validateNewUser with little change to your data structure:
userData = {
profile: {
name: 'Artem',
account : {
type : 'free'
}
},
username: 'aaa#gmail.com',
password: '123'
};
Then you should be able to access data you need.
As far as I remember there were some discussion on meteor forum about removing profile field, that's why I'm solving this kind of problems in different way. For me Meteor.users is collection which should not be changed for sake of peace in mind - it could be changed by future version of meteor. My approach require to write more code in the beginning, but later it pays off, because you have place to store data about user and Meteor.users collection has docs with minimal amount of data.
I would use jagi:astronomy#0.12.1 to create schema and custom methods. In general I would create new collection UserAccounts with schema:
UserAccount = new Astro.Class( {
name: 'UserAccount',
collection: 'UserAccounts',
fields: {
'userId' : {type: 'string'},
'type' : {type: 'string', default:'free'}
},
} )
and add schema to Meteor.users :
User = new Astro.Class( {
name: 'User',
collection: Meteor.users,
fields: {
'services' : {type: 'object'},
'emails' : {type: 'array'}
},
methods:{
account : function(){
return UserAccounts.findOne({userId:this._id})
}
}
} )
The usage looks like this:
var user = Meteor.users.findOne();
user.account().type
In summary:
Accounts.onCreateUser : always allow to create user account and always create UserAccount which corresponds to it ( with field userId)

Firebase / AngularFire create user information

I'm creating a new user with AngularFire. But when I sign the user up I also ask for first name and last name and I add that info after registration.
$firebaseSimpleLogin(fbRef).$createUser($scope.signupData.email, $scope.signupData.password).then(function (user) {
// Add additional information for current user
$firebase(fbRef.child('users').child(user.id).child("name")).$set({
first: $scope.signupData.first_name,
last: $scope.signupData.last_name
}).then(function () {
$rootScope.user = user;
});
});
The above code works, it creates node fin Firebase (users/user.id/ ...).
The problem
When I login with the new user I get the user default information: id, email, uid, etc. but no name. How can I associate that data automatically to the user?
You can't. Firebase hides the complexity of login management by storing the login details in its own datastore. This process knows nothing of your app's forge, which means it doesn't know if or where you're storing any additional user information. It returns the data that it does know about as a convenience (id, uid, email, md5_hash, provider, firebaseAuthToken).
It's up to your app to then take the [u]id and grab whatever app specific user information you need (such as first name, last name). For an Angular app, you'd want to have a UserProfile service which retrieves the data you're looking for once you get the authentication success broadcast.
Also, in your snippet, consider changing
.child(user.id)
to
.child(user.uid)
This will come in handy if you ever support Facebook/Twitter/Persona authentication later on. uid looks like "simplelogin:1" - it helps to avoid unlikely but possible id clashes across providers.
I have the same issue on this and feel like noone actually has a clear answer (2 years on). But here is the rough structure of how such a service could look like:
app.factory('Auth', function(FURL, $firebaseAuth, $firebaseObject, $rootScope, $window){
​
var ref = new Firebase(FURL);
var auth = $firebaseAuth(ref);
​
var Auth = {
user: {},
​
login: function(user){
return auth.$authWithPassword({
email: user.email,
password: user.password
});
},
​
signedIn: function(){
return !!Auth.user.provider;
},
​
logout: function(){
return auth.$unauth;
}
};
​
// When user auths, store auth data in the user object
auth.$onAuth(function(authData){
if(authData){
angular.copy(authData, Auth.user);
// Set the profile
Auth.user.profile = $firebaseObject(ref.child('profile').child(authData.uid));
Auth.user.profile.$loaded().then(function(profile){
$window.localStorage['gym-key'] = profile.gym.toString();
});
} else {
if(Auth.user && Auth.user.profile){
Auth.user.profile.$destroy();
}
​
}
});
​
return Auth;
});

Resources