I am developing a React Native App with expo-cli. I am using ADFS to authenticate users in the app, and in particular, I am using MSAL.js. I've got the authentication working on web, however, I cannot get the App to run on iOS or Android. I am getting the error: "The cache location provided is not valid. Provided value: localStorage. Possible values are: localStorage, sessionStorage."
I've tried using sessionStorage instead of localStorage, but I just get the same error, except it tells me the provided value was sessionStorage.
I found a thread where someone was having a similar problem as well, here: MSAL UserAgentApplication: Local storage not working. I tried the workaround, but then I get the error: "undefined is not a constructor (evaluating 'new Msal.Storage("localStorage")')".
If I try calling Msal.Storage("localStorage"), it tells me that msal.Storage is not a function.
I've also tried explicitly passing in values to the UserAgentApplication function from my config, rather than passing in the config itself:
var myMSALObj = new Msal.UserAgentApplication(msalConfig.auth.clientId, msalConfig.auth.authority, msalConfig.auth.redirectURI, { cacheLocation: 'localStorage' });
Here's the snippet of code where I believe the problem is:
const msalConfig = {
auth: {
clientId: "<client-Id>",
authority: "<authority>",
redirectURI: "http://localhost:19006"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
//new Msal.Storage("localStorage");
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
I would expect the call to UserAgentApplication to correctly use the localStorage as cache on both iOS and Android, but this does not seem to be the case. The app works perfectly in my browser on my laptop, though, without any cache/localStorage hiccups. Are there any workarounds/fixes for this that do not require Msal.Storage? Any help is much appreciated!
I just wrote my own library to do it. I guess MSAL.js doesn't have the right calls to work with mobile browsers.
https://yarnpkg.com/en/package/azure-ad-graph-expo
The library below does just what I want, which is log into Azure AD, retrieve a token, and then use that token to query MS graph for the appropriate user data.
Related
I am struggling with a tutorial from Hasura which I believe might never have worked. I think it might be missing a piece and I'm interested in the most appropriate way to bridge the gap between when I've created and what will work.
From that documentation, as well as the Auth0 VueJS documentation I have been able to create a working VueJS application that authenticates via my Auth0 project and gives me back a user profile complete with user photo and email. So that's all working.
Following the Hausura tutorial from this I added apollo. I am now able to access non-protected GraphQL queries from my API but not protected ones. The reason is pretty clearly the code I added from the tutorial here:
getAuth: () => {
// get the authentication token from local storage if it exists
// return the headers to the context so httpLink can read them
const token = localStorage.getItem('apollo-token')
if (token) {
return 'Bearer ' + token
} else {
return ''
}
},
A console.log shows that token is null even when I'm logged in. It's pretty obvious why too. localStorage was never updated to contain a token. So of course it's null. There's no place in the tutorial that suggests this should be set anywhere but clearly it should.
So I did some digging and in that file src/vue-apollo.js there is an onLogin and onLogout function which require an ApolloClient object and in the case of the login, also a token. This will set the local storage appropriately.
Currently, my login is done within src/view/Home.vue like so:
login() {
this.$auth.loginWithRedirect()
// ... maybe do something here ?
},
Now if I were to add something like this after the login, it will not actually be called because of the redirect. But I think I need to add something like this:
this.$auth.getTokenSilently(token => {
onLogin(this.$apolloProvider.defaultClient, token)
}
Perhaps I need to add this into like the mounted() or beforeCreate() live cycle hook in my src/App.vue? I'm really not sure how to proceed here. Does this tutorial even really work?
Furthermore, isn't storing an auth token in localStorage a bad idea because of cross site request forgery?
You are right about it not being detailed in the post. I've not run the project but following the link to the source code of the demo. The /callback route creates the key via Callback component at /app/src/router.js:25, it add an authentication guard as well.
Then on /app/src/components/Callback.vue:25 on created handleAuthentication from auth service is called.
created() {
this.$auth.handleAuthentication();
}
You can check the code in /app/src/auth/authService.js it uses the auth0 library instance to get the token and call localLogin that saves the token in line 92.
About the security, I would follow the advice in Token Storage from Auth0.
I've written several Google Cloud Endpoints in Python and have followed the directions to require that calls to them come from users authenticated using Firebase. I need to call my Endpoints from a web app using JavaScript, but I can't seem to get the authentication working.
I'd like to use the Google APIs client (gapi) which comes with the added benefit of dynamically generating the client library from a provided discovery document. When I try using the gapi client, I can make the call to my API just fine, but I get an HTTP 401 as a response, along with the HTTP unauthorized message that my python source returns.
Google's documentation on the subject is rather sparse. I gather from one tutorial on the subject that a standard Ajax call can be used, but I don't see any documentation on how to call a Firebase authenticated endpoint from Gapi. My current concern is that the gapi client may not be set up (yet) to allow for the use of a discovery doc and also allow for the Authorization header to be set as Firebase Auth requires.
Is what I'm attempting even possible?
Any suggestions would be appreciated. Perhaps calling a Firebase Authenticated endpoint isn't possible using the gapi client.
Here's a rough outline of my gapi js code:
function(token) {
gapi.client.init({
apiKey: 'MY_API_KEY',
discoveryDocs: [MY_DISCOVERY_DOC_URL'],
clientId: 'MY_WEB_CLIENT_ID',
scope: 'profile'
}).then(function(){
return gapi.client.my.server.api.call();
}).then(function(response){
console.log(response.result.data)
}, function(reason){
console.log('Error: ' + reason.result.error.message)
});
}
I have been struggling with this for a while now and finally made it work. I found two options:
Option 1) If you want to use the gapi.client library:
There is a method called gapi.client.setToken(tokenObject) - documentation
However, it seems to be new (July '17) and little documentation or examples are available. I made it work doing the following (this is in angularJS with angular-fire but I hope you get what I am doing, basically ignore the "$scope")
// any time auth state changes, add the user data to scope
$scope.auth.$onAuthStateChanged(function (firebaseUser) {
$scope.firebaseUser = firebaseUser;
$scope.idToken = null;
// get the token from the firebase User Object
// Note that getToken() is deprecated and for me it did not work as desired
// use getIdToken() instead
firebaseUser.getIdToken().then(function (idToken) {
$scope.idToken = idToken;
});
});
// Now you can use setToken
// If from the docs you were thinking firebase's getIdToken() gives me TokenObject and gapi's setToken()
// expects a TokenObject so I'll just pass it - you'd be wrong! (at least for me - if it works for you please give me a heads up)
// You'll need to build your own token:
var homemadeToken = {
access_token: $scope.idToken.toString() // This feels so wrong
};
gapi.client.setToken(homemadeToken);
gapi.client.yourapi.getSomething().execute(function (resp) {
// Do stuff with the response
}
);
Option 2) Use jQuery's Ajax request - documentation
$.ajax(backendHostUrl + '/_ah/api/yourapi/v1/someendpoint', {
headers: {
'Authorization': 'Bearer ' + $scope.idToken // Here it worked without making a string first but I did not check why
},
method: 'GET',
success: function (resp) {
// Do stuff with the response
}
});
If after all of that your backend is still not accepting the tokens and you have migrated from endpoints v1 to v2, it might help migrating again as described here. Esp. make sure the lib folder is created again.
Even after SDK updates, I noticed that if and once you migrated from v1 to v2 the "lib" folder is never updated regardless of whether or not it hase been updated.
Still not working?
This github page fixes the issue on the BACKEND side for an earlier version - the backend did not accept firebase tokens and needed to be hacked. If you want to apply the changes as described there and you are using the latest "lib" folder's (writing in July '17) users_id_token.py as per migration guide, note that the file has changed and you need to go against the explicit commentary in that file's _verify_signed_jwt_with_certs method:
# Formerly we would parse the token body here.
# However, it's not safe to do that without first checking the signature.
and parse the token before checking the signature. From that file's comments it can be inferred however, that Google plans to put the entire logic elsewhere - hopefully firebase friendly and safely.
Currently, using 1.6.0 of the scripts and CSS files. I have the widget integrated in my Angular 1.5.x app. I have been experiencing issues receiving 404 when trying to render the OKTA Widget as its GET request to:
https://*.oktapreview.com/api/v1/sessions/me
Which means that the widget gets rendered once and I am able to log in, but once I log out, I am not able to re-render the widget without having to refresh the browser first.
Has anyone found and resolved this issue?
I think this might be related to this Stack Overflow issue. Can you check your privacy settings?
The reason for this issue is because the widget exchanges the sessionToken (which you get after logging in) with an id_token via a hidden iframe, at which time the session cookie is also set. But, since this is in an <iframe>, it's considered a third-party cookie.
There is currently no good way around this when your browser has third-party cookies disabled without redirecting to Okta to set the session cookie. A new version (1.8.0) of the widget is going to be released this week will make this easier (See this commmit which addresses this issue). With this new version, passing in authParams.display = 'page' will perform the call to /authorize via a redirect rather than through the hidden iframe.
Now that 1.8.0 is available via Okta's CDN, here is a sample of how to use the authParams.display = 'page' setting described above:
<script>
var config = {
baseUrl: 'https://YOUR-OKTA-ORG-HERE.okta.com',
clientId: 'YOUR-OKTA-CLIENT-ID-HERE',
redirectUri: 'YOUR-REDIRECT-URL-HERE',
authParams: {
responseType: 'code',
display: 'page',
scopes: ['openid', 'email', 'profile'],
}
};
var oktaSignIn = new OktaSignIn(config);
oktaSignIn.renderEl({ el: '#app-container' }, () => {});
</script>
In our current SPA implementation we authenticate against AzureAD using adal.js and upon successful authentication hit our web api to get the authorization data. There are a couple of edge case scenarios where the get authorization data call could fail. In this case we would like to clear out the state/cache created by adal.js. I have tried a few things but I have not not been able to create a clean slate. Here is some code that I have tried.
localStorage.clear();
var authContext = AuthenticationContext.prototype._singletonInstance;
authContext.clearCache();
authContext._user = null;
I don't want to use the built in logout function. Calling logout redirects the user to the Azure signout page. The UX is pretty wierd so trying to avoid it.
If you want to clear all the cache entries created by adal, clearCache() is the method that should be used, and if you want to clear the cache only for a specific resource entry, then use clearCacheForResource.
But one more thing to note is, these two methods only clear the cache/storage though, it won't clear any session/cookie hold on azure ad, if you want to clear that, then the built in logout should be the one to use.
You could probably try to implement the silent logout(probably using iframe, this will prevent the ux from displaying), and then call clearCache to clear the localstorage/sessionstorage
You can set postLogoutRedirectUri to your aplication setup:
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'www.contoso.com',
clientId: '0101001010101',
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
endpoints: endpoints,
postLogoutRedirectUri: 'https://www.yourapp.com'
},
$httpProvider
);
I'm using the official Dropbox JS library in a Node.js server. It only ever needs to authenticate as a single user, and it can't go through the whole OAUTH browser setup every time the server starts. I am attempting to write an auth driver that pretends to be like the NodeServer driver, but runs the callback straight away with a code that always stays the same.
Here's what I've got (it's coffeescript, but you get the idea):
myAuthDriver = {
authType: -> return "code"
url: -> return "http://localhost:8912/oauth_callback" # What the url would be if I were using NodeServer
doAuthorize: (authUrl_s, stateParam, client, callback) ->
authUrl = url.parse(authUrl_s, true)
callback({
code: "[a code I just got using the NodeServer driver]"
state: authUrl.query.state
})
}
Running authenticate with this driver set causes this error:
Dropbox OAuth error invalid_grant :: given "code" is not valid
The docs say that this should only occur with a broken auth driver (but it doesn't give any ideas for fixing it).
Does anyone with more knowledge of OAUTH or Dropbox know what's wrong here?
Note: I've found in several places online that Dropbox OAUTH codes never expire
Once you have an OAuth 2 access token, you can just do var client = new Dropbox.Client({token: '<your token>'});. No need for an auth driver at all.
(If you want an easy way to get an access token, consider using https://dbxoauth2.site44.com.)