We have a Xamarin Forms code base that has been using MSAL since 1.1.4 in 2019, we are now using 4.22.0.
The app has always kept it's own copy of the accesstoken acquired either silectly or interactively and it is the app's copy that it uses when accessing various B2C secured apis.
It only calls AcquireTokenSilent or AcquireTokenInteractive on initial logon or when a 401 is detected.
We beleive we have an issue with the storing of token and from what I understand MSAl caches the access token itself anyway. Looking at the documentation and samples we should be calling AcquireTokenSilent whenever we need the token and handling MSALUIRequiredException to call AcquireTokenInteractive.
Is this correct, does MSAL cache the accesstoken?
When we build the PublicClientApplication we do not call currently call WithIosKeychainSecurityGroup. Is that a prerequisite for the cachhing of tokens to work on iOS?
PublicClientApplicationBuilder
.Create(ClinicalServicesSettings.ClientID)
.WithB2CAuthority(ClinicalServicesSettings.Authority)
.Build()
You should use the recommended call pattern of AcquireTokenSilent, catch the MsalUiRequiredException and call AcquireTokenInteractive.
MSAL does handle the caching for you, in mobile applications. During AcquireTokenSilent, MSAL will check the cache to see if there is a valid account and if the user can be signed-in silently without being shown a UI. If the refresh token is expired, or there has been a conditional access policy applied to the account (like MFA), then the MsalUiRequiredException will be thrown.
If you want to share tokens across apps belonging to the same TeamId on iOS, you need to use WithIosKeychainSecurityGroup, which sets the iOS key chain security group. The TeamId is appended to the security group and by default the security group com.microsoft.adalcache is used, which is where MSAL caches tokens after auth (regardless of whether it happened in the app or via the authenticator - however auth via authenticator is not available with B2C). This enables the tokens to be shared across apps, and with both ADAL and MSAL iOS apps.
Related
I've got a mobile app that uses a native oauth flow via react-native-oauth (which makes use of an inAppBrowser) to complete the oath flow for ios. When it's complete, I have the tokens and idToken natively.
What I'd like to do now is to pass these to my backend web server, so that I can kick start some calendar sync process for this user. However, my backend is using MSAL.NET with a custom TokenCache, so expects the tokens to be present in this cache. To achieve this on the web app, there is an oauth flow baked into the web app that is hooked into this MSAL setup, and thus works:
// Hook the auth code up to the token cache, that will ensure it gets into the database (msal_token_cache)
TokenCache cache = new MsaldbTokenCache(userId, db).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(opts.ClientId,
settings.Core.BaseUrl.UrlCombine(redirectUrl),
new ClientCredential(opts.ClientSecret),
cache,
null);
AuthenticationResult result =
await cca.AcquireTokenByAuthorizationCodeAsync(code, new[] {"Calendars.ReadWrite"});
I've no idea how to do this with my ios-authed tokens. I can't allow the web server to acquire the tokens via auth code exchange since it was initiated in IOS...
Can anyone help?
I would design the solution to be architecturally separated so that it supports both web and mobile, which should work fine as long as both clients use the correct scopes for OAuth redirects:
A Rest API triggers the calendar sync process for the user received in the access token
One client of the Rest API is the back end of a web app - which uses MSAL.Net - and perhaps issues cookies to the browser
Another client of the Rest API is the mobile app - which uses React Native OAuth
I would avoid aiming to mix the 2 clients together though. If the web back end requires auth cookies then the mobile client will not be able to provide them.
The back end for the mobile app needs to a Rest API endpoint and it's possible that the existing back end may need to be refactored a little, to separate web client concerns from API concerns.
Based on the documentation details for MSAL (Azure AD) mentioned at : https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-supports-multiple-application-architectures-and-multiple-platforms#msalnet-is-about-acquiring-tokens-not-protecting-an-api
I am working on POC : ASP.NET Web App accessing Custom REST API protected by Azure AD.
The link mentioned above says that MSAL.NET (Microsoft Authentication Library for .NET) enables developers of .NET applications to acquire tokens in order to call secured Web APIs. These Web APIs can be the Microsoft Graph, other Microsoft APIS, 3rd party Web APIs, or your own Web API.
Based on the above definition is it good to assume that MSAL will be helping in maintaining a token cache and refreshes tokens for you when they are close to expire in case of the Custom REST APIs also.
Can anyone help me here by providing their guidance on it further.
MSAL and Azure AD don't give special treatment to MS Graph API tokens (except the ability to use a short name for scopes).
It can refresh any of the tokens as long as the way to refresh is still valid.
In the case of implicit grant (used by MSAL.js 1.x), the user must have an active AAD session in the browser.
In most other cases you use a refresh token to get a new token; as long as the refresh token is still valid, you can get a new token even after the access token has expired.
Context
I'm building a hybrid native/web app. Part of it will be API driven, and part of it will be webviews showing an existing website.
Once logged in with my OIDC SDK (Amplify/AppAuth), I can access the user's id_token and access_token.
However, because the webviews used in the SDK are not controllable, I cannot reuse the cookies generated by my identity server.
Question
For the reason above, I'm trying to login a user from their id_token or their access_token. (both are JWTs)
I believe this isn't supported by IdentityServer4, but I am looking to implement something myself.
What I have found so far:
1) id_token_hint
OpenID Connect Core 1.0, Section 3.1.2.1, id_token_hint parameter:
OPTIONAL. ID Token previously issued by the Authorization Server being passed as a hint about the End-User's current or past authenticated session with the Client. If the End-User identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it SHOULD return an error, such as login_required. When possible, an id_token_hint SHOULD be present when prompt=none is used and an invalid_request error MAY be returned if it is not; however, the server SHOULD respond successfully when possible, even if it is not present. The Authorization Server need not be listed as an audience of the ID Token when it is used as an id_token_hint value.
If the ID Token received by the RP from the OP is encrypted, to use it as an id_token_hint, the Client MUST decrypt the signed ID Token contained within the encrypted ID Token. The Client MAY re-encrypt the signed ID token to the Authentication Server using a key that enables the server to decrypt the ID Token, and use the re-encrypted ID token as the id_token_hint value.
According to this optional spec of OpenID, I should be able to use the id_token on the /authorize endpoint to login the user. I know this isn't implemented in IdentityServer4, but I'm looking at AddCustomAuthorizeRequestValidator to do that. However I'm not sure how to "get a user from their id_token" in the code. Do you have any pointers?
2) using AddJwtBearerClientAuthentication
This method sounds like I could authenticate from my access_token, but I can't find much documentation on how to use it.
THE PROBLEM
Let me know if I am misunderstanding requirements, but it feels like you have 2 different apps and are trying to make them feel like a single integrated set of screens.
The default behaviour will be as follows since web views are private browser sessions and cannot use system browser cookies. You want to prevent the second step since it is a poor user experience:
User signs in to the mobile app
Whenever a secured web view is invoked, there is a new private browser session with no auth cookie, therefore requiring a new login
COMMON APPROACH: SUPPLY A TOKEN TO THE WEB VIEW
It can be common to forward the mobile token to the web view, if the two apps are part of the same user experience. If required, extend the mobile app's scopes to include those for the web app.
You should not need to issue any new tokens or change the security model of either app. But the mobile and web apps need to work together on a solution.
POSSIBLE WEB VIEW SOLUTION FOR A SERVER SIDE WEB APP
This might be the simplest option:
Provide new .Net Core web back end entry point URLs that require tokens instead of cookies
The mobile app could then call those endpoints from web views, and supply its own token in the Authorization Header of the web view request
The web back end could then forward mobile web view requests to your Web APIs
The code to add the Authorization Header to a web view request may be a little tricky but there are ways to do it:
Android
iOS
POSSIBLE WEB VIEW SOLUTION FOR AN SPA
An option that works for Cookieless SPAs is for the web view to ask the mobile host app for an access token via a Javascript bridge. Some code of mine does this for a cookieless SPA:
Web UI Code to Call the Mobile Host
Android Code
iOS Code
You can clone / run the mobile github repos from your local PC to see the solution. I will be writing up some notes on this in my blog's Mobile Web Integration Post.
My React app uses Firebase Authentication and the Real-Time Database. I now need to access 3rd party services using OAuth 2.0. To do this securely, I am using the OAuth 2.0 Authorization Code Flow with Firebase Functions as my back-end:
React app gets an Authorization Code from the 3rd party.
Authorization code is passed to a Firebase function to get an access token.
But this is where I am stuck. How do I save the access token in the backend so that the React app can access the 3rd party resources? I don't want to store the access token in the React app because that's not secure.
How do I maintain a session in the back-end using Firebase Functions?
Should the session be tied to the React app instance or the Firebase user? The former approach would require a session cleanup. The latter has the advantage that new access tokens can be obtained using a refresh token. This way the user never has to login to the 3rd party again!
After trying bunch of different approaches, I ended up storing the access token & the refresh token in the back-end, both tied to the firebase user. The tokens are only accessible through firebase functions. This approach has the following advantages:
Secure
I don't have to worry about sessions and session cleanup.
I can refresh the access token whenever I want using the refresh token.
OVERALL FACTORS
Typically these are the areas that your SPA Security choices influence, and there are trade offs:
Type of App
Strong Security
Hosting and Global Performance
Technical Simplicity
IN ALL CASES
Your app should:
Maintain a token for each user separately
You will want to identify users from tokens after login
OPTION 1: FRONT END MODEL / COOKIELESS
This involves using tokens in the browser and has these benefits:
Back end is static content only
Web resources can be deployed close to users via a CDN
You can use the OIDC Client library to manage security
This is widely used and should only be exploitable if your app has cross site scripting vulnerabilities. Some resources of mine:
SPA Goals
In Memory Authentication Code
Code Sample Write Up
OPTION 2: BACK END MODEL / AUTH COOKIES
This involves proxying via a back end and carrying tokens around in authentication cookies. I would use this model if developing an online banking app but for medium security apps it can add a lot of overhead. You may have to write more security code and it is possible to end up with a less secure solution than option 1:
Requires a proxy back end that runs code
The back end code may need to be clustered and globally distributed, to achieve good performance
You need to protect the auth cookie and deal with cross site request forgery risks
Out of interest the respected mod_auth_openidc library can be helpful for this type of solution.
BEST OF BOTH WORLDS
If using tokens in a browser is deemed unacceptable I would aim to follow option 1 as much as possible. Then implement option 2 as an additive step:
Deploy web static content separately to the code that does back end token management
Continue to use the OIDC Client library, and make the Authorization Code Exchange call your back end
The only difference to option 1 is that tokens are not available to browser code
Firebase Rest API mentions that we can pass CREDENTIAL to provide access to authenticated nodes. However I was not able to find documentation on where I can find these credential or generate these credential. Custom tokens generated using NodeJS firebase-admin client also don't work.
https://firebase.google.com/docs/database/rest/save-data
https://docs-examples.firebaseio.com/rest/saving-data/auth-example.json?auth=CREDENTIAL
If you scrolled down a little on the same page, you would find the answer:
In the following example we send a POST request with an auth parameter, where CREDENTIAL is either our Firebase app secret or an authentication token...
Firebase secrets are legacy credentials you can find/create under Project settings - Service Accounts in the Console. Using one as the auth parameter gives the caller administrative access.
Or you can use a service account to generate admin level access tokens instead of relying on the legacy secrets. See here for the Java implementation.
Or if you have an authenticated user – for example you're implementing an API a client apps call via HTTP, passing along their current access token –, you can use that token directly to impersonate the user.
The custom authentication tokens serve a completely different purpose and are part of a different sign in flow. Therefore they do nothing via the REST API.