spring boot authorization server wildcard redirection list - spring

I'm currently migrating my oauth server to the Spring Authorization Server and want to use the code workflow. My frontend is a Angular App and I use there the angular-oauth2-oidc plugin. More or less it's kind of working, but I have troubles with the redirect URI.
When I do the Authorization Server Configuration I have to register my client application with redirectUris.
RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("foo")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("https://foo.planyourtrip.travel")
.scope(OidcScopes.OPENID)
.build()
My problem is, that I offer the login button on different pages and I want to redirect the user to the former page after he logged in. I could register all client pages there, but some of them have a dynamic path and it would be a lot of pages. So this is not a option for me.
ATM, there is a Exception in OAuth2AuthorizationCodeRequestAuthenticationProvider
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
I tried already to add parameters to the redirect uri, but this wasn't working too.
I'm thinking about adding a login path, where I load the origin path from a cookie, but this seems to be a lot of work and I'm surprised that there is no wildcard solution for this.
I hope someone can help me and give me some advice. Thanks in advance!

I would recommend using the state part of OIDC for this. The library has built in support.
For example, suppose you have an Angular guard that protects any route and sends people to your Auth server along these lines:
#Injectable()
export class AuthGuardWithForcedLogin implements CanActivate {
constructor(private authService: AuthService) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> {
return this.authService.isDoneLoading$.pipe(
filter(isDone => isDone),
switchMap(_ => this.authService.isAuthenticated$),
tap(isAuthenticated => isAuthenticated || this.authService.login(state.url)),
);
}
}
The state.url part passed to login(...) should be passed along in the URL and arrive in your app again after the user gets back. You can then grab it in your login sequence and forward the user to the intended page:
this.oauthService.loadDiscoveryDocument()
.then(() => this.oauthService.tryLogin())
.then(() => {
this.isDoneLoadingSubject$.next(true);
if (this.oauthService.state && this.oauthService.state !== 'undefined' && this.oauthService.state !== 'null') {
let stateUrl = this.oauthService.state;
if (stateUrl.startsWith('/') === false) {
stateUrl = decodeURIComponent(stateUrl);
}
console.log(`There was state of ${this.oauthService.state}, so we are sending you to: ${stateUrl}`);
this.router.navigateByUrl(stateUrl);
}
})
That way you can have just one solid "redirect" url in your Authorization Server, and persist intended route via that state.
You can clone, install, and run my sample repository to see this in action.
Hope that helps!

Related

Vue router navigation guard prevent url from being changed

I'm using a vuejs navigation guard to do some checks to check if a user is valid to enter a page. If the user doesn't meet the criteria I want the auth guard to return false which it currently does. However, when this is done the browser url gets set back to the previous url I came from. I don't want this behaviour instead I want the browser url to stay the same but still keep the user unable to use the page.
The reason I want this is because when the user hits refresh I want it to stay on the same page. I know this is intended vuejs behaviour for the router but I'm hoping to find a way around it. Here is the code for auth guard.
function guardRoute (to, from, next) {
if (window.$cookies.get('kiosk_mode') === new DeviceUUID().get()) {
return next(false)
}
return next()
}
To reject a navigation but to not reset the url to its previous state you can reject the navigation with (requires vue 2.4.0+):
next(new Error('Authentication failure'));
And if you don't have router error handling then you need to include:
router.onError(error => {
console.log(error);
});
See documentation for more details: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
Try this
history.pushState({}, null, '/test') before the return next(false);

How to do Role-based Web API Authorization using Identity Server 4 (JWT)

This is all new to me and I'm still trying to wrap my head around it. I've got an IDP (Identity Server 4) set up, and I was able to configure a client to authenticate to it (Angular 6 App), and further more to authenticate to an API (Asp.Net Core 2.0). It all seems to work fine.
Here's the client definition in the IDP:
new Client
{
ClientId = "ZooClient",
ClientName = "Zoo Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = true,
RedirectUris = { "http://localhost:4200/home" },
PostLogoutRedirectUris = { "http://localhost:4200/home" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
"roles",
"ZooWebAPI"
}
}
I'm requesting the following scopes in the client:
'openid profile email roles ZooWebAPI'
The WebAPI is set up as such:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors();
services.AddDistributedMemoryCache();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44317";
options.RequireHttpsMetadata = false;
options.ApiName = "ZooWebAPI";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(policy =>
{
policy.WithOrigins("http://localhost:4200");
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowCredentials();
policy.WithExposedHeaders("WWW-Authenticate");
});
app.UseAuthentication();
app.UseMvc();
}
By using [Authorize] I was successfully able to secure the API:
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public ActionResult Get()
{
return new JsonResult(User.Claims.Select(
c => new { c.Type, c.Value }));
}
}
Everything works fine, if client is not authenticated, browser goes to IDP, requires authentication, redirects back with access token, access token is then used for API calls that are successfully made.
If I look at the Claims in the User object, I can see some information, but I don't have any user information. I can see the scopes, and etc, but no roles for example. From what I read, that is to be expected, and the API should not care about what user is calling it, but how would I go by restricting API calls based on roles? Or would that be completely against specs?
The IDP has an userinfo end point that returns all the user information, and I thought that would be used in the WebAPI, but again, from some reading, it looks like the intention is for that end point to be called from the client only.
Anyway, I would like to restrict Web API calls based on the roles for a specific user. Does anyone have any suggestions, comments? Also, I would like to know what user is making the call, how would I go by doing that?
JWT example:
Thanks
From what I can learn from your information, I can tell the following.
You are logging in through an external provider: Windows Authentication.
You are defining some scopes to pass something to the token that indicates access to specific resources.
The User object you speak of, is the User class that gets filled in from the access token. Since the access token by default doesn't include user profile claims, you don't have them on the User object. This is different from using Windows Authentication directly where the username is provided on the User Principle.
You need to take additional action to provide authorization based on the user logging in.
There a couple of points where you can add authorization logic:
You could define claims on the custom scopes you define in the configuration of Identityserver. This is not desirable IMHO because it's fixed to the login method and not the user logging in.
You could use ClaimsTransformation ( see links below). This allows you to add claims to the list of claims availible at the start of your methods. This has the drawback ( for some people an positive) that those extra claims are not added to the access token itself, it's only on your back-end where the token is evaluated that these claims will be added before the request is handled by your code.
How you retrieve those claims is up to your bussiness requirements.
If you need to have the user information, you have to call the userinfo endpoint of Identityserver to know the username at least. That is what that endpoint is intended for. Based on that you can then use your own logic to determine the 'Roles' this user has.
For instance we created an separate service that can configure and return 'Roles' claims based upon the user and the scopes included in the accesstoken.
UseClaimsTransformation .NET Core
UseClaimsTransformation .NET Full framework

Map IdentityServer4 to "/identity", then map UI

My application is basically this one: https://github.com/IdentityServer/IdentityServer4.Quickstart.UI
But I'm trying to map it to "/identity" using this piece of code:
app.Map("/identity", builder => { builder.UseIdentityServer(); });
It's working well, I can access /identity/.well-known/openid-configuration successfully.
However, if I try to connect, the application redirects me to /identity/account/login, which is on the IdentityServer side. IdentityServer cannot find my controller, so it returns me 404.
I tried to the LoginUrl property:
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/bleh/account/login";
})
But it also returns 404.
I also tried to make the Quickstart controller route the same as the redirect one:
[Route("identity/account/login")]
But it also returns 404.
Any idea?
If anyone is still looking for this. This worked for me:
app.Map("/identity", builder => {
builder.UseStaticFiles();
builder.UseIdentityServer();
builder.UseMvcWithDefaultRoute();
});

Why am I automatically pushed to login page when a user is unauthenticated by calling the Web API?

I am using MVC 6 web api and I have a method (shown below). When the user is not authenticated (logged on) and makes the call (example url: https://localhost:44338/api/account/Test), they get automatically pushed to url:
https://localhost:44338/Account/Login?ReturnUrl=%2Fapi%2Faccount%2FTest
BUT this is a web api project and does not have any views (such as im automatically getting pushed to here).
Code:
[HttpGet("test")]
[Authorize]
public async Task<IActionResult> Test()
{
return Json("success");
}
Why am I getting automatically pushed to the login page? I've NOTHING in my startup.cs or web.config that specifies this. It seems like default behaviour? How do I disable this so I just get the 401 status?
Thanks in advance!
For convenience, here's the solution that worked for me:
services.Configure<IdentityOptions>(o =>
{
o.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
I was pointed to this article: mvc6 unauthorized results in redirect instead by #TrevorWard
ASP.NET automatically redirects to the login page, if you have AuthorizationAttribute defined in your App_Start/FilterConfig.cs, see if AuthorizeAttribute() is defined. If it does, remove it.
Check if you have App_Start/Startup.Auth.cs. If it does, delete it.
If Startup.cs is decorated with the attribute [assembly: OwinStartupAttribute(typeof(FleetSys.Startup))], remove that attribute.
You are probably using wrong Authorize attribute.
In MVC, you should use System.Web.Mvc.AuthorizeAttribute and that one will redirect.
In Web API, you should use System.Web.Http.AuthorizeAttribute and that one will return 401 status code.

Laravel 5 and Socialite - New Redirect After Login

Another newb question here, but hopefully someone can shed some light:
I am using Socialite with Laravel 5, and I want to be able to redirect the user to a page on the site after they have logged in. The problem is that using
return redirect('any-path-I-put-here');
simply redirects back to 'social-site/login?code=afkjadfkjdslkfjdlkfj...' (where 'social-site' is whatever site is being used i.e. facebook, twitter, google, etc.)
So, what appears to me to be happening is that the redirect() function in the Socialite/Contracts/Provider interface is overriding any redirect that I attempt after the fact.
Just for clarification, my routes are set up properly. I have tried every version of 'redirect' you can imagine ('to', 'back', 'intended', Redirect::, etc.), and the method is being called from my Auth Controller (though I have tried it elsewhere as well).
The question is, how do I override that redirect() once I am done storing and logging in the user with socialite? Any help is appreciated! Thank you in advance.
The code that contains the redirect in question is:
public function socialRedirect( $route, $status, $greeting, $user )
{
$this->auth->login( $user, true );
if( $status == 'new_user' ) {
// This is a new member. Make sure they see the welcome modal on redirect
\Session::flash( 'new_registration', true );
return redirect()->to( $route );// This is just the most recent attempt. It originated with return redirect($route);, and has been attempted every other way you can imagine as well (as mentioned above). Hardcoding (i.e., 'home') returns the exact same result. The socialite redirect always overrides anything that is put here.
}
else {
return redirect()->to( $route )->with( [ 'greeting' => $greeting ] );
}
}
... The SocialAuth class that runs before this, however, is about 500 lines long, as it has to determine if the user exists, register new users if necessary, show forms for different scenarios, etc. Meanwhile, here is the function that sends the information through from the Social Auth class:
private function socialLogin( $socialUser, $goto, $provider, $status, $controller )
{
if( is_null( $goto ) ) {
$goto = 'backlot/' . $socialUser->profile->custom_url;
}
if( $status == 'new_user' ) {
return $controller->socialRedirect($goto, $status, null, $socialUser);
}
else {
// This is an existing member. Show them the welcome back status message.
$message = 'You have successfully logged in with your ' .
ucfirst( $provider ) . ' credentials.';
$greeting =
flash()->success( 'Welcome back, ' . $socialUser->username . '. ' . $message );
return $controller->socialRedirect($goto, $status, $greeting, $socialUser);
}
}
I managed to workaround this problem, but I am unsure if this is the best way to fix it. Similar to what is stated in question, I got authenticated callback from the social media, but I was unable to redirect current response to another url.
Based on the callback request params, I was able to create and authenticate the user within my Laravel app. It worked good so far but the problems occured after this step when I tried to do a return redirect()->route('dashboard');. I tried all the flavours of redirect() helper and Redirect facade but nothing helped.
The blank page just stared at my face for over 2 days, before I checked this question. The behaviour was very similar. I got redirect from social-media to my app but could not further redirect in the same response cycle.
At this moment (when the callback was recieved by the app and user was authenticated), if I refreshed the page manually (F5), I got redirected to the intended page. My interpretation is similar to what's stated in this question earlier. The redirect from social-media callback was dominating the redirects I was triggering in my controller (May be redirect within Laravel app got suppressed because the redirect from social-media was still not complete). It's just my interpretation. Experts can throw more light if they think otherwise or have a better explaination.
To fix this I issued a raw http redirect using header("Location /dashboard"); and applied auth middleware to this route. This way I could mock the refresh functionality ,redirect to dashboard (or intended url) and check for authentication in my DashboardController.
Once again, this is not a perfect solution and I am investigating the actual root of the problem, but this might help you to move ahead if you are facing similar problem.
I believe you are overthinking this. Using Socialite is pretty straight forward:
Set up config/services.php. For facebook I have this:
'facebook' => [
'client_id' => 'your_fb_id',
'client_secret' => 'your_fb_secret',
'redirect' => '>ABSOLUTE< url to redirect after login', //like: 'http://stuff'
],
Then set up two routes, one for login and one for callback (after login).
In the login controller method:
return \Socialize::with('facebook')->redirect();
Then in the callback function
$fb_user = \Socialize::with('facebook')->user();
// check if user exists, create it and whatnot
//dd($fb_user);
return redirect()->route('some.route');
It should be pretty much similar for all other providers.
We are using the Socialite login in our UserController as a trait. We simply overrode the AuthenticatesSocialiteLogin::loginSuccess() in our controller.
use Broco\SocialiteLogin\Auth\AuthenticatesSocialiteLogin;
class UserController extends BaseController
{
use AuthenticatesSocialiteLogin;
public function loginSuccess($user)
{
return redirect()->intended(url('/#login-success'));
}
....

Resources