The problem is related to WebSocket auth using JWT.
I know how to create a regular API call using route (HTTP requests), authenticate it using JWT and is working fine.
The thing that is bothering me is how to use JWT when working with WebSockets? The authenticate will not work with JWT like with HTTP requests. I have researched how but hit a dead end.
The question is how do i authenticate WebSocket routes using JWT? Any pointers are appreciated or if i am missing the point how it works would you be kind to explain what I am understanding wrong.
I figured out that wrapping the WebSocket route in authenticate will actually require the token in the header. From there I can do the same as i do with route (get the data from the call which is made to establish the connection) and close the socket if the user is not authenticated. Will leave it here if anyone gets stuck with this.
routing {
authenticate {
webSocket("/ws") {
val principal = call.principal<JWTPrincipal>()
val email = principal?.payload?.getClaim("email")?.asString()
val expiresAt = principal?.expiresAt?.time ?: System.currentTimeMillis()
val users = userService.getAllUsers()
val randNum = Random(System.currentTimeMillis()).nextInt(0, 2)
if (randNum % 2 == 0) {
close(CloseReason(CloseReason.Codes.PROTOCOL_ERROR, "Please auth the user"))
}
incoming.consumeAsFlow()
.mapNotNull {
(it as? Frame.Text)?.readText()?.let { data ->
Json.decodeFromString<User>(data)
}
}.collect {
send(Frame.Text(userService.insertUser(it).toString()))
}
}
}
}
Related
I am currently building a web app based on a turborepo (monorepo) in which I want to use Discord OAuth login with next-auth. Therefore I have two modules web and api, where api is my express backend with discord.js. The web app is basically a dashboard for a Discord bot.
I figured that next-auth only provides client side authentication. So my question is how can I validate the OAuth session from the client side in the best manner?
My middleware for express currently looks like this:
function throwUnauthorized(res: Response) {
res.status(401).json({ code: 401, message: 'Unauthorized' });
}
export async function isAuthorized(req: Request, res: Response, next: NextFunction) {
try {
const authorization = req.headers.authorization;
if (!authorization) {
return throwUnauthorized(res);
}
// validate token with Discord API
const { data } = await axios.get('https://discord.com/api/oauth2/#me', {
headers: { Authorization: authorization },
});
// protect against token reuse
if (!data || data.application.id !== process.env.TC_DISCORD_CLIENT_ID) {
return throwUnauthorized(res);
}
// map to database user
let user = await User.findOne({ id: data.user.id });
user ??= await User.create({ id: data.user.id });
data.user.permissions = user.permissions;
req.user = data.user;
next();
} catch (error) {
return throwUnauthorized(res);
}
}
In this approach the Discord OAuth Token would be send via the Authorization header and checked before each request that requires Authorization. Which leads to my problem: The token needs to be validated again causing multiple request to Discord API.
Is there a better way to handle this? Because I need to map Discord user profiles to database profiles. I read that you could try decode the jwt session token from next-auth, but this did not work when I tested it.
Maybe there is a whole different project structure suggested for my project. But I thought I should separate the api and web-app since I would have needed a custom express server because it includes the Discord bot and Prometheus logging functions. I am open for suggestions and your thoughts!
I have a working ServiceStack API that authenticates against a AzureAD tenant. We are trying to move this to start using Azure B2C. The application is build with c# and runs on net 5.0. I've managed to change the configuration to use the 'correct' config. I'm then using Postman to get my access token from my tenant suing the authorization code flow.
However, when i make a request to the api, the response is always a 401 status code.
Where in the servicestack code can I put a break point to see why this failure is happening? I have tried multiple places in our AppHostConfigurator.cs/AppHost.cs files, but the break points doesn't appear to display why a 401 is being sent back as a response. I'm sure it's something related to wrong claims/roles expected etc, maybe the Azure ADB2C application being setup incorrectly, but obviously i need to know exactly so that i can resolve.
I'm setting up the authentication like this:
private static void ConfigureAuthentication(IAppHost host)
{
var authProviders = new List<IAuthProvider> {new NetCoreIdentityAuthProvider(host.AppSettings)};
if (host.AppSettings.GetAllKeys().Contains("AzureAdB2C"))
{
var debugMode = host.AppSettings.Get(nameof(HostConfig.DebugMode), false);
var azureSettings = host.AppSettings.Get<AzureAdB2COptions>("AzureAdB2C");
var jwt = azureSettings.GetB2CJWTProviderReader(debugMode);
jwt.PopulateSessionFilter = (session, payload, request) =>
{
if (session.Email == null && payload.ContainsKey("upn") && payload["upn"].Contains("#"))
session.Email = payload["upn"];
if (session.UserName == null && payload.ContainsKey("unique_name"))
session.UserName = payload["unique_name"];
};
authProviders.Add(jwt);
}
var auth = new AuthFeature(() => new AuthUserSession(), authProviders.ToArray())
{
HtmlRedirect = "/account/signin",
HtmlLogoutRedirect = "/account/signout",
IncludeAssignRoleServices = false,
IncludeRegistrationService = false
};
// remove default service authentication services
auth.ServiceRoutes.Remove(typeof(AuthenticateService));
host.Plugins.Add(auth);
}
We are using swagger as well to call the API (which works as expected). This question is more about that requests that are submitted with a bearer token.
thanks
Please refer to this existing answer for examples of how to validate why a 3rd Party JWT Token is invalid with ServiceStack's JWT Auth Provider.
I want to create login and logout methods and routes. I've done already basic authentication but now I'm stuck how to continue. How should I do that, should I use sessions?
I'm using Vapor 3, Swift 4 and PostgreSQL and followed this tutorial https://medium.com/rocket-fuel/basic-authentication-with-vapor-3-c074376256c3. I'm total newbie so I appreciate a lot if you can help me!
my User model
struct User : Content, PostgreSQLModel, Parameters {
var id : Int?
private(set) var email: String
private(set) var password: String
}
extension User: BasicAuthenticatable {
static let usernameKey: WritableKeyPath<User, String> = \.email
static let passwordKey: WritableKeyPath<User, String> = \.password
}
UserController.swift, registering user.
private extension UserController {
func registerUser(_ request: Request, newUser: User) throws -> Future<HTTPResponseStatus> {
return try User.query(on: request).filter(\.email == newUser.email).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "a user with this email already exists" , identifier: nil)
}
let digest = try request.make(BCryptDigest.self)
let hashedPassword = try digest.hash(newUser.password)
let persistedUser = User(id: nil, email: newUser.email, password: hashedPassword)
return persistedUser.save(on: request).transform(to: .created)
}
}
}
So in Basic authentication there is no 'logout' per se as there's no login. With HTTP Basic Auth you transmit the user's credentials with each request and validate those credentials with each request.
You mention sessions, but first it's important to know what type of service you are providing? Are you providing an API or a website? They are different use cases and have different (usually) methods for authentication and login.
For an API you can use Basic Authentication and generally in your login function you exchange the credentials for some sort of token. Clients then provide that token with future requests to authenticate the user. To log out you simply destroy the token in the backend so it is no longer valid.
For a website, things are a little different since you can't manipulate the requests like you can with a normal client (such as setting the Authorization header in the request). HTTP Basic authentication is possible in a website, though rarely used these days. What traditionally happens is you submit the user's credentials through a web form, authenticate them and then save the authenticated user in a session and provide a session cookie back to the browser. This authenticates the user in future requests. To log a user out you just remove the user from the session.
Vapor's Auth package provides everything you need to do both of these scenarios. See https://github.com/raywenderlich/vapor-til for examples of both
I've successfully included a jwt auth token in my application and able to restrict access to the endpoints. I want to embed the user's ID in the jwt token, but I'm struggling how to implement the jwt verifier in Ktor.
I create a token for the client something like this:
val token = JWT.create().withAudience(audience).withIssuer(issuer).withClaim("userId", "XXX").sign(algorithm)
The route is setup like this. The authentication {} block is run on server startup and does not allow creating a verifier with the userId.
This was derived from the jwt sample:
route("/api") {
authentication {
val jwtVerifier = makeJwtVerifier(issuer, audience)
jwtAuthentication(jwtVerifier, realm) { credential ->
if (credential.payload.audience.contains(audience))
JWTPrincipal(credential.payload)
else
null
}
handle {
// Handle jwt succcess here
}
}
private fun makeJwtVerifier(issuer: String, audience: String): JWTVerifier = JWT
.require(algorithm)
.withAudience(audience)
.withIssuer(issuer)
.build()
What is the correct way to do this? I understand I'd need to create a verifier for each request, but don't really know where to do this nor if that is desirable.
You should implement it in here. You don't need a verifier that checks user ids.
jwtAuthentication(jwtVerifier, realm) { credential ->
if (credential.payload.audience.contains(audience))
val userId = credential.payload.claims["userId"].asString()
// check if user exists ... if you want
JWTPrincipal(credential.payload)
else
null
}
Is there an alternative to the session feature plugin in servicestack? In some scenarios I cannot use cookies to match the authorized session in my service implementation. Is there a possibility to resolve the session using a token in http header of the request? What is the preferred solution for that in case the browser is blocking cookies?
I'm using ServiceStack without the built-in auth and session providers.
I use a attribute as request filter to collect the user information (id and token), either from a cookie, request header or string parameter.
You can provide this information after the user takes login. You append a new cookie to the response and inject the id and token info on clientside when rendering the view, so you can use for http headers and query parameters for links.
public class AuthenticationAttribute : Attribute, IHasRequestFilter
{
public void RequestFilter(IHttpRequest request, IHttpResponse response, object dto)
{
var userAuth = new UserAuth { };
if(!string.IsNullOrWhiteSpace(request.GetCookieValue("auth"))
{
userAuth = (UserAuth)request.GetCookieValue("auth");
}
else if (!string.IsNullOrEmpty(request.Headers.Get("auth-key")) &&
!string.IsNullOrEmpty(request.Headers.Get("auth-id")))
{
userAuth.Id = request.Headers.Get("id");
userAuth.Token = request.Headers.Get("token");
}
authenticationService.Authenticate(userAuth.Id, userAuth.token);
}
public IHasRequestFilter Copy()
{
return new AuthenticationAttribute();
}
public int Priority { get { return -3; } } // negative are executed before global requests
}
If the user isn't authorized, i redirect him at this point.
My project supports SPA. If the user consumes the API with xmlhttprequests, the authentication stuff is done with headers. I inject that information on AngularJS when the page is loaded, and reuse it on all request (partial views, api consuming, etc). ServiceStack is powerful for this type of stuff, you can easily configure your AngularJS app and ServiceStack view engine to work side by side, validating every requests, globalizing your app, etc.
In case you don't have cookies and the requests aren't called by javascript, you can support the authentication without cookies if you always generate the links passing the id and token as query parameters, and pass them through hidden input on forms, for example.
#Guilherme Cardoso: In my current solution I am using a PreRequestFilters and the built-in session feature.
My workflow/workaround is the following:
When the user gets authorized I took the cookie and send it to the client by using an http header. Now the client can call services if the cookie is set in a http-header (Authorization) of the request.
To achieve this I redirect the faked authorization header to the cookie of the request using a PreRequestFilter. Now I am able to use the session feature. Feels like a hack but works for the moment ;-)
public class CookieRestoreFromAuthorizationHeaderPlugin : IPlugin
{
public void Register(IAppHost appHost)
{
appHost.PreRequestFilters.Add((req, res) =>
{
var cookieValue = req.GetCookieValue("ss-id");
if(!string.IsNullOrEmpty(cookieValue))
return;
var authorizationHeader = req.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.ToLower().StartsWith("basictoken "))
{
var cookie = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Split(' ').Last()));
req.Cookies.Add("ss-id",new Cookie("ss-id",cookie));
req.Items.Add("ss-id",cookie);
}
});
}
}