Configure Silent Authentication in Open ID Connect - authentication

client type: Spa
grant type: implicit or code(pkce)
As a user, I want to be able to get silently authenticated if I have already logged with my identity provider. If not stay on the client side just like a guest user. And if I want to login to the client I should be able to get authenticated manually through the login page.
This has both manual sign-in and automatic sign-in scenarios. How would you handle such cases in Open ID Connect?
By adding the prompt=none in client settings will silently get a new token if user has a valid session. But if not I want the user to be able to manually authenticate through the login page upon his/her wish.
If I set prompt=none this will never have any user interaction such as authentication.
tags: Silent authentication oidc, automatic login, SSO

It is quite a deep subject, and the flow typically works like this:
CLASSIC OIDC SOLUTION
User is redirected for each SPA
If signed in already at the IDP there is no login prompt
OAuth state is stored in local storage (though it is recommended to only store actual tokens in memory)
When an access token expires (or before) do an iframe token renewal with prompt=none
When a new browser tab is opened do an iframe token renewal to get tokens for that tab - to avoid a full redirect
When the user logs out remove OAuth state from local storage
The most widely used library is OIDC Client which will do a lot of the hard work for you. See also my blog post + code sample for how this looks visually.
PROBLEM AREAS
It is worth being aware also that iframe silent renewal does not work by default in the Safari browser in 2020. Some notes on this here.

Alternatively, you can use signinSilent(). I have used it on my login page ngOnInit (since AuthGuard will anyway redirect the user to login, I thought it will be the perfect place in my scenario).
// login.ts
ngOnInit(): void {
this.authService.signinSilent().then(_ => {}).catch(_ => {});
}
// authService
public signinSilent() {
return this.userManager.signinSilent();
}
signinSilent method will return the user object if user already has a valid session with idp. else it will throw an error, probably login_required.

Related

How to use two factor authorization cookie along with single factor authorization cookie

I have a web application with singe factor authorization and now have implemented two-factor authorization. I am using the Microsoft Identity for the log-in. The problem is - so far we have used a cookie to remember the user while providing the username and password. Say that as 'signglefactorcookie'. Now on the authenticator application authorization page(TFA), I have added another cookie for the remember me option. Say that as 'twofactorcookie'. Now how can I make my client request for both cookies when I use the below code?
// Check whether there is a valid session or persistent cookie
if(this.User.Identity.IsAuthenticated){
// Move to a landing page
}
Problem scenario
Now the problem is, if I log in to the single-factor authentication page with the correct user name and password and land at the two-factor authentication page.
Open a new tab and try to access the home page getting success since there is a single factor cookie that is recognized which makes the user authorized.
How can I make it in a standard way?

Expo: WebBrowser.openAuthSessionAsync and related calls skip user input even when browser session expired

This is a summary of an issue I filed directly with expo (it ws closed but I have asked for it to be reopened):
This issue happens whether using AuthSession.startAsync,
AppAuth.authAsync or WebBrowser.openAuthSessionAsync on iOS in
local development and published release (expo managed). Haven't tried
on standalone build yet.
Steps to Reproduce
user presses 'sign in' button, (app calls one of the above methods to kick start authentication session with a Salesforce oauth provider)
user enters credentials successfully
app goes through oauth redirects and returns user to our app and we get our access token.
user presses 'sign out' button (app calls revoke endpoint for token, then calls server endpoint to delete any browser cookie sessions for given account reference)
app navigates to sign in screen
user presses 'sign in' again (app calls the same method from above to start the authentication session with Salesforce oauth provider again)
instead of opening the sign in page, the app redirects itself back with an access token as if the user had successfully entered their credentials, even though any cookies/session data the browser stores should be invalid and necessitate a sign in.
Expected Behaviour
steps 1 - 5 are all as expected. Step six should be
app redirects to Salesforce oauth provider sign in page, in unauthenticated state (ie no cookie or session data that was previously stored is still valid)
user is required to re-enter their credentials
oauth flow takes over and redirects the user into the app if the credentials were correct.
Actual Behavior
as per initial steps where the user is not even asked to enter their credentials (step 6):
instead of opening the sign in page, the app redirects itself back with an access token as if the user had successfully entered their credentials, even though any cookies/session data the browser stores should be invalid and necessitate a sign in.
Reproducible Demo
The code is in a private repo so I can't share details of it, but it's a very standard oauth flow, and seeing it's happening in all three of the method calls from the top suggests to me that it may be due to something in the WebBrowser.openAuthSessionAsync implementation. I have seen on the apple developer docs that SFAuthenticationSession has been deprecated in favour of ASWebAuthenticationSession. My understanding is that this (SFAuthenticationSession) is the browser used by expo's WebBrowser and the wrappers mentioned above (AppAuth and AuthSession) for the oauth interactions. I also see that it mentions it's for a one-time login, which perhaps explains why it would hold onto any session data and jump to the conclusion of re-authenticating without directly seeking credentials from the user, but it seems unhelpful to store a cookie without validating it, which is what appears to be the end result.
Notes
Essentially this is making it impossible for a user to sign out of our app, because the system browser, that we don't have control over, is keeping track of their authentication despite the session value no longer being valid against the server.
I've seen other people looking to find ways to clear cookies from the system browser, which may be what this issue relates to, though it doesn't appear to be possible to access the auth session's browser cookies in any way. This comment on a GitHub issue is exactly what I'm experiencing and need to find a solution to.
I would like users to be able to sign out, and then when they sign back in again they should have to enter their credentials again. Does anyone have any thoughts as to how this might be possible?
On iOS, it's now possible to pass in the following config to WebBrowser.openAuthSessionAsync to essentially treat it as incognito and ensure it doesn't retain any cookies. The effect is that the user will have to re-authenticate each time (even if there session is still active). I'm not aware of a similar approach for Android, however.
Code
const browserOptions = {
preferEphemeralSession: true
}
result = await WebBrowser.openAuthSessionAsync(authUrl, redirect, browserOptions)

Authentication with AzureAD via TestCafe Tests

I'm unable to authenticate / sign-in via AzureAD when running testCafe.
const testrole = Role(
'https://login.microsoftonline.com/',
async t => {
await t
.typeText(Selector('input').withAttribute('type', 'email'), *******)
.click(Selector('#idSIButton9'))
.typeText(Selector('input').withAttribute('type', 'password'), ********)
.click(Selector('#idSIButton9'));
},
{ preserveUrl: true }
);
The above steps work fine, however after entering the password I get a message saying:
"Unable to sign in to Outlook account, Error: AADSTS900561: The endpoint only accepts POST requests. Received a GET request."
From my initial search, it seems like something to do with 3rd party cookies on the browser. However, I'm unable to find a solution at this time.
Any idea how I get around this issue?
The Azure AD product team has always reminded me that it is a bad idea to try to automate sign in like that.
They will probably detect that you are a bot and start blocking your requests, even if you succeed.
Instead, to acquire access tokens you need to use either the client credentials flow (for app-only tokens) or the resource owner password credentials flow (for delegated user tokens).
Client credentials flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
ROPC flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
You have to take good care to secure the credentials used for testing.
And use a test tenant if possible.

auth0 still auto-logs in seamlessly even after calling /logout url

Simple problem, I want to login and out of an app with various users to check different app functionality. App is using Auth0 for user management.
I am calling the /v2/logout url as a part of my flow.
But somehow, after logging out, when I login again the seamless SSO behavior runs and I'm immediately logged in again with no prompts -- it's as if the logout URL was never called.
Only way to get a login prompt again, is to clear my browser cache. Is there an auth0 cookie somewhere I need to delete as well? Or am I missing something? I'm reading the seamless SSO docs but don't see anything beyond calling /v2/logout.
Calling the Auth0 /v2/logout API endpoint will log the user out of Auth0 and optionally the IdP (if you specify federated parameter). It will not log out the user from your Application so you will need to implement that in your application.
Here in the Javascript SPA example, in the setSession() we are storing the Access token(along with its expiry) and the ID token in localStorage. In the logout() function we are then removing these entries. This is logging out from the Application user session. You can optionally redirect to /v2/logout to clear the Auth0 and IdP session as well in this function. That way, when you are checking if user is authenticated, the isAuthenticated() returns false and we force the user log in again.
So turns out, the issue is around redirecting the user as opposed to calling the logout url directly. I was using a separate ajax api call to the logout url. However when I use window.location.replace(logoutUrl), the logout actually happens.
From the auth0 docs:
To force a logout, redirect the user to the following URL:
https://YOUR_AUTH0_DOMAIN/v2/logout
Redirecting the user to this URL clears all single sign-on cookies set by Auth0 for the user.
So a separate call doesn't work -- have to redirect. Which I suppose makes sense -- a separate ajax call doesn't have the user session context.

Possible to validate ServiceStack's authentication cookie client side?

I am having a HTML (Angular) site which has a login button and needs (of course) to present a different GUI when the user is authenticated. I am using ServiceStack based REST services. Now when a user is successfully authenticated I was wondering if it is possible to check the generated authentication cookie (by ServiceStack) on the client only. I just need to check the userid, maybe role and expiration date of the cookie. Advantage is I do not have to make 'CheckUserIsAuthenticated' server rest call's just for showing a different GUI (of source CRUD actions are validated serverside).
You can check that a cookie exists with document.cookie, as it's hard to work with directly Mozilla provides a lightweight cookies wrapper to make it easier to work with, likewise AngularJS provides $cookies provider.
But a cookie doesn't tell you anything about the user since even non-authenticated / anonymous users will have cookies. Instead to check if the user is authenticated you can call /auth route via ajax which when authenticated ServiceStack will return summary info about the user, e.g:
{
UserId: "1",
SessionId: "{sessionId}",
UserName: "user#gmail.com",
DisplayName: "First Last"
}
If the user is not authenticated /auth will return 401 Unauthorized.