I'm setting up Auth0 with SSO enabled for multiple web sites.
I have it working perfectly for username/password connections and for google connections where the user is only logged into a single google account.
But... I have two google accounts logged in on my browser. When I go to site 1 and use the google connection, it prompts me to select a google account to log in with. That's correct.
But when I go to site 2, and attempt to stay logged in using sso data from Auth0, it still prompts me to select a google account again. Which I would not expect.
Here's my sign in code:
auth0.getSSOData(function (err, ssoData) {
if (!err && ssoData.sso) {
auth0.signin({
connection: ssoData.lastUsedConnection.name,
scope: 'openid email email_verified name app_metadata',
state: 'http://localhost:21763/#/',
authParams: {
prompt: 'none'
}
});
}
});
I added prompt: 'none' having read this older post on Auth0 community, but to no avail. https://auth0.com/forum/t/sso-login-with-google-connection-must-select-account/4983
Has anyone else done this? I feel like it must be possible.
Thanks!
I started experimenting with the possible parameters into the signin method call and stumbled across the answer...
I tried a few things, but this seems to work perfectly, moving the "prompt: 'none'" up out of the authParams to be a direct option to signin method call...
auth0.getSSOData(function (err, ssoData) {
if (!err && ssoData.sso) {
auth0.signin({
connection: ssoData.lastUsedConnection.name,
scope: 'openid email email_verified name app_metadata',
state: 'http://localhost:21763/#/',
prompt: 'none'
});
}
});
Related
I'm trying to add Google authentication to my Azure Functions app which will be used from a Svelte static web app (SWA). The SWA uses Google Identity (https://accounts.google.com/gsi/client) to both authenticate and then retrieve an access_token. Authentication is performed using a standard Google Identity sign in button. I've tried One Tap prompt as well with the same result.
google.accounts.id.initialize({
client_id: googleClientId,
callback: handleCredentialResponse,
});
google.accounts.id.renderButton(
button,
{ theme: 'outline', size: 'large' }, // customization attributes
);
User authenticates, works fine and I get a JWT id_token containing name email image etcetera. It's a bit annoying the user has to then again go through the whole process of selecting their account, but I guess that's the Google experience. Once I'm ready to do function calls I then proceed to authorize:
function getAccessToken() {
return new Promise((resolve, reject) => {
const client = google.accounts.oauth2.initTokenClient({
client_id: googleClientId,
scope: "openid",
callback: (response) => {
if (response.access_token) {
resolve(access_token);
} else {
reject(response?.error);
}
},
});
client.requestAccessToken();
});
}
This also works fine, I retrieve an access_token. I then proceed to call an Azure Function with this token in the header:
Authorization: Bearer <ACCESS_TOKEN>
This always results in a 401 response. I have tried setting all functions to anonymous to no effect.
I'm wondering if this has to do with scope. In the Google Console it's only possible to add Google specific scopes, which is why I retrieve an access_token for the openid scope.
I've also tried setting credentials to include since there might be cookies the Easy Auth layer would like to read from the web app to authenticate the user. CORS on the Azure Functions app is configured correctly for the host names used by the web app and Access-Control-Allow-Credentials is enabled on the Function App. This has no effect either.
Wow this was badly documented. After reading the Azure Functions and App Service Authentication blog post it seems an 'authentication token' needs to be retrieved from the functions app itself instead of an 'access token' from Google. After Google identification the id_token from the first step needs to be POSTed to https://<functions_app>/.auth/login/google with the following as body:
{
"id_token": "<id_token>"
}
This in turn returns something as follows:
{
"authenticationToken": "<authenticationToken>",
"user": { "userId": "<sid>" }
}
This authenticationToken then needs be be passed in the header to each function call as follows:
X-ZUMO-AUTH: <authenticationToken>
Edit: it seems this was fully documented, somehow I missed this.
I've previously setup and followed these steps, and got it to work:
https://firebaseopensource.com/projects/firebase/quickstart-js/auth/chromextension/readme/#license
But cloning this extension, but with a different Extension ID, different firebase instance and OAuth/key setup, I've tried to follow the steps at least 3 separate times, but every time it fails at the last step, the login (the consent screen works though)
Upload a fresh dummy extension (without key field in manifest.json) (But it is the exact same code as the working one)
Get the Chrome Extension ID, and the Public Key, no problem
Create OAuth Client ID with the Extension ID, configured consent screen, no problem, the screen shows up and I can click through
Add OAuth & Public Key to manifest.json
Make another OAuth Client ID? (I think this is a duplicate step, because which Client ID should I use? and afaik the whitelisting is optional)
Use chrome.identity to get OAuth token:
export function startAuth(interactive) {
// Request an OAuth token from the Chrome Identity API.
chrome.identity
.getAuthToken({ interactive: !!interactive }, (token) => {
if (chrome.runtime && !interactive) {
console.error('It was not possible to get a token programmatically.');
}
else if (token) {
// Authorize Firebase with the OAuth Access Token.
const credential = firebase.auth.GoogleAuthProvider
.credential(null, token);
firebase.auth()
.signInWithCredential(credential)
.catch((error) => {
// The OAuth token might have been invalidated. Lets' remove it from cache.
if (error.code === 'auth/invalid-credential') {
chrome.identity
.removeCachedAuthToken({ token }, () => {
startAuth(interactive);
});
}
});
}
else {
console.error('The OAuth Token was null');
}
});
}
Note: This code is working with another extensionID/OAuth/key, so the code itself can't be the problem.
There isn't much to change between them, it's really the ExtensionID, the OAuth client ID url, and the public key.
I've followed the steps 3 times, to no avail, every time I get the error auth/invalid-credential. I do get a token though, but that token won't authenticate. How to find out why this is?
It's trying to post to this address, and returning error 400:
POST: https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=xxxxx
Error: INVALID_IDP_RESPONSE : Invalid Idp Response: access_token audience is not for this project
My conclusion
There must be something changed with how to set this up, even though it works with different credentials
The problem was that I didn't create the OAuth in the same project in google cloud console, as the firebase project...
This has been baffling me for hours now, so I have been trying to get EasyAuth working using different providers.
I am using this on Azure Functions, so let's say my function address is
https://xxx.azurewebsites.net
If I want to login into the service using a Google account I send my post request along with token received from Google to the following address
https://xxx.azurewebsites.net/.auth/login/google
This gives me a converted token back.
However if I do the same thing with a Microsoft account using the following details
Request Body:
{ "access_token": "token-string-value" }
Endpoint:
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It gives me the following error instead of a converted token
401 Unauthorized You do not have permission to view this directory or page.
--
I am using Msal JavaScript library to get my authentication token. Also I am testing these in Postman which makes it easy to understand what the problem is before I deal with the code and other stuff.
-- Update 1.0
This does seem like a bug, as even if I try to navigate to the
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It shows me the following
This URL works for other providers, Google, Facebook and Twitter. For all of them it redirects the user to the provider's login page.
According to the error page and the address bar contents, the client doesn't exist which could be referring to the application created on Azure to allow my website access the API. But everything has been setup correctly.
It would be helpful if someone from Azure We App Services can take a look at this.
I have created the Application and added the application ID and Secret int eh App Services page.
-- Update 2.0
So after hours of investigation, I managed to get the URL working, shockingly it was due to wrong information given on Azure portal. The link in Authorization and Authentication section of App Service is pointing to a new platform to register applications, which is purely for Azure AD based users.
For the external users to be able to login the application need to be registered in the following portal
https://apps.dev.microsoft.com
After registering the application here, and added the details in the App Service blade, the URL to EasyAuth is working.
However this doesn't resolve my issue. I still need a JavaScript library that gives me valid token which I can pass to EasyAuth endpoint.
Strangely the token taken from MSAL is not valid for Microsoft account. It just gives me the same error that my access is unauthorised. This means I probably need to use a different library to get a different token. I'd appreciate it if still someone can help me with this.
Below is a short sample code I am using to retrieve token and pass it to another function n which call EasyAuth endpoint and post the token along.
var applicationConfig = {
clientID: "xxxx-xxx-xxxx-xxxx",
authority: "https://login.microsoftonline.com/9fc1061d-5e26-4fd5-807e-bd969d857223",
graphScopes: ["user.read"],
graphEndpoint: "https://graph.microsoft.com/v1.0/me"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{ storeAuthStateInCookie: true, cacheLocation: "localStorage" });
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
// accessToken
}, function (error) {
console.log(error);
});
}
I managed to find what was causing the problem.
So basically only Live Connect SDK generated tokens are valid on
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
We were using MSAL which was generating tokens valid only on Azure Active Directory. I have been in touch with Azure Support, and have asked them to update the documentation. It currently is very confusing as none of these have been explained in the EasyAuth documentations.
We decided to go with Azure AD B2C, as it's more reliable and turns out cheaper for us.
In case anyone would like to use EasyAuth with Microsoft Account, the following is showing how to get access token from Live SDK
WL.Event.subscribe("auth.login", onLogin);
WL.init({
client_id: "xxxxxx",
redirect_uri: "xxxxxx",
scope: "wl.signin",
response_type: "token"
});
WL.ui({
name: "signin",
element: "signin"
});
function onLogin(session) {
if (!session.error) {
var access_token = session.session.access_token;
mobileClient.login('microsoftaccount', { 'access_token': access_token }, false)
.then(function () {
console.log('TODO - could enable/disable functionality etc')
}, function (error) {
console.log(`ERROR: ${error}`);
});
}
else {
console.log(`ERROR: ${session.error_description}`);
}
}
Reference to
< script src="//js.live.net/v5.0/wl.js">
Good day! I’m trying to implement a Passwordless login using auth0 node package. Basically I’m trying to send magic link through email without getting the email value in the browser, so I’m getting the email from an API.
Note: The email that has been pulled from other API is already registered in Auth0
The problem was when I receive the link in my Inbox and click it, I’m getting the Opt-in that I should allow the app to access the profile, which is not the path that I’m expected to see. So here’s my code:
const AuthenticationClient = require('auth0').AuthenticationClient
app.get('/sendmagiclink', function(req, res) {
let auth0 = new AuthenticationClient({
domain: [Auth0 Domain],
clientId: [Auth0 Client ID],
clientSecret: [Auth0 Client Secret]
})
var data = {
email: 'myemail#gmail.com',
send: 'link',
authParams: {
connection: [My Connection]
} // Optional auth params.
};
auth0.passwordless.sendEmail(data, function (err) {
if (err) {
// Handle error.
}
});
})
Also, another problem with my code is the connection name which automatically sets to email rather than the custom connection name I created.
Your thoughts would be greatly appreciated.
I assume you are getting the Consent screen here. Are you using a localhost URL? In that case it's not possible to skip it, but it won't appear when your app is in production, or if you set up a domain in /etc/hosts file. More info here: https://auth0.com/docs/api-auth/user-consent#skipping-consent-for-first-party-clients
The name of the passwordless email connection is usually email.
I'm trying to create a rest api for a service I'm working on.
The service has two parts to it - the website and the mobile client. Basically, the mobile device keeps its location up to date via the api, the website displays the data via the api.
Seeing as my application only targets Android, I'm hoping to use 'Sign in with Google' as the authentication mechanism for both the mobile and website clients.
The API is using Node.js and Express.js. I'm running into trouble when generating new user accounts though. Seeing as I don't want to trust data from the client, my expected sign up process was something like this:
Through the website:
User visits website, hits 'Sign up with Google'.
User accepts the app request to see their Google details.
Website gets a google auth token back, which it sends to the api.
API contacts google with that auth token to get the user details.
API creates a new user in the database along with my own form of access token.
API returns my own access token to the client for future request signing.
Through the Android app:
User downloads the app, runs and hits 'Sign up with Google'.
User accepts authorisation step presented by google.
App gets a token, which it sends to the API.
API contacts google with that auth token to get the user details.
API realises the user exists and registers this new device with that user.
API returns my own access token to the app for future request signing.
I'm running into a lot of trouble here as soon as the token gets to the server though. Every time I use the token generated, I just get an error 'Invalid Credentials'.
Initially I started to use Passport.js, but what I found was this. In the documentation it states setup happens like so:
passport.use(new GoogleStrategy({
returnURL: 'http://www.example.com/auth/google/return',
realm: 'http://www.example.com/'
},
function(identifier, profile, done) {
User.findOrCreate({ openId: identifier }, function(err, user) {
done(err, user);
});
}));
But when I log the contents of 'identifier' it is actually something like
https://www.google.com/accounts/o8/id?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I assume the ID is something unique to me but I can't seem to discover exactly what it is. Furthermore I don't know if it is time-limited or will last forever. As a final problem, I don't even know if I can get that same value when signing up via Android because I don't know where the value comes from. It's not the kind of API access token that I was expecting. When I output the contents of profile, it's just my name and email address - nothing that I can use for contacting the Google API with to verify the user.
The above solution I don't like anyway because it means the server hosting the client site has to make an api request in order to pass the id to the api. Or it sends the id details to the client so that it can pass them on to the api server. Or, the website server puts it into the api database, which is also a bad solution.
So next I figured I would use the javascript library from the Google sign in docs. I have something like this:
Website Client:
<script type="text/javascript">
function createUser(token)
{
$.ajax({
url:"http://api.example.com/user",
dataType: 'jsonp',
data: 'token='+token,
success:function(json){
alert("Success: "+json);
},
error:function(jqXHR, textStatus, errorThrown){
alert("Error "+textStatus+" "+errorThrown);
}
});
}
function signinCallback(authResult)
{
if(authResult['access_token'])
{
document.getElementById('signinButton').setAttribute('style', 'display: none');
alert('RES: '+JSON.stringify(authResult));
createUser(authResult['access_token']);
}
else if(authResult['error'])
{
alert('There was an error: ' + authResult['error']);
}
}
</script>
Node API user handling function:
function(req, res)
{
var callback = req.query.callback;
if(callback == null)
{
res.send("{valid:false}");
}
else
{
var token = req.query.token;
if(token == null)
{
res.send("{valid:false}");
}
else
{
var oauth2Client = new OAuth2Client('xxxxxx', 'xxxxxx', '');
oauth2Client.credentials = {
access_token: token
};
googleapis
.discover('plus', 'v1')
.execute(function(err, client){
if(client == null)
{
console.log("Client is null");
}
else
{
var request1 = client.plus.people.get({ userId: 'me' })
.withApiKey('xxxxxx');
request1.execute(function(err, result){
console.log("Result: " + (err ? err.message : result.displayName));
});
}
});
res.set('Content-Type', 'text/javascript');
res.send(callback+"({ret:'User Test'});");
}
}
}
This works fine on the client side - I see the alert box with my access token and some other details. The trouble is that when I call the google api functions on my api server for getting the user details, I get 'Invalid Credentials' returned. I assume this is because I generated the access token in javascript for a website and I'm using it from somewhere else.
So, that pretty much leaves me out of ideas. Is there an easy way to achieve this that I'm missing? I just want to be able to generate a token from a website and from an Android app that I can use on the server for validating the user's details. The generated token doesn't even need to work on the website or the Android app, just from the api server. The API server can't do the process of directing the user to Google for authorisation though because the user doesn't directly interact with it.