juhanakristian / remix-auth-microsoft

Microsoft authentication strategy for remix-auth
MIT License
37 stars 19 forks source link

Accessing the access token from loader/action #20

Open flexwie opened 1 year ago

flexwie commented 1 year ago

I was wondering how I could access the access token from a loader or action function to call an upstream API. Do I have to add it to the session in the authenticator callback?

franklinjavier commented 1 year ago

@flexwie you can see a practical example here https://github.com/juhanakristian/remix-auth-microsoft/issues/1

flexwie commented 1 year ago

That would mean that I have to store it somewhere myself when authenticating a user, right?

franklinjavier commented 1 year ago

@flexwie In the Session/Cookie. eg:

// session.server.ts
import { createCookieSessionStorage, redirect } from '@remix-run/node'

import type { Session } from '@remix-run/node'
import type { UserProfile } from './auth.server'
import { LOGIN_URL } from '~/utils/constants'

// export the whole sessionStorage object
export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: 'eratus-session',
    sameSite: 'lax', // this helps with CSRF
    path: '/', // remember to add this so the cookie will work in all routes
    httpOnly: true, // for security reasons, make this cookie http only
    secrets: [process.env.SESSION_SECRET],
    secure: process.env.NODE_ENV === 'production', // enable this in prod only
  },
})

export const { commitSession, destroySession } = sessionStorage

export function getSession(request: Request): Promise<Session> {
  return sessionStorage.getSession(request.headers.get('Cookie'))
}

export async function getSessionUser(request: Request) {
  const session = await getSession(request)
  return session.get('user') as UserProfile
}

export async function getLoggedInUser(request: Request) {
  const user = await getSessionUser(request)
  if (user) return user
  throw needLogin(request)
}

export async function needLogin(request: Request) {
  const { pathname, search } = new URL(request.url)
  const session = await getSession(request)
  session.set('redirect', `${pathname}${search}`)
  return redirect(LOGIN_URL, {
    headers: {
      'Set-Cookie': await commitSession(session),
    },
  })
}
// auth.server.ts

import { Authenticator } from 'remix-auth'
import { MicrosoftStrategy } from 'remix-auth-microsoft'

import type { MicrosoftProfile } from 'remix-auth-microsoft'
import { getUser } from '~/models/user.server'
import { sessionStorage } from '~/services/session.server'
import { decodeToken } from '~/services/token.server'

export interface UserProfile extends MicrosoftProfile {
  employeeId: number
  isActive: boolean
  roles: string[]
}

export const authenticator = new Authenticator<UserProfile>(sessionStorage)

const baseURL =
  process.env.APP_URL ||
  (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000')

const microsoftStrategy = new MicrosoftStrategy(
  {
    clientId: process.env.MS_CLIENT_ID || '',
    clientSecret: process.env.MS_CLIENT_SECRET || '',
    redirectUri: `${baseURL}/auth/microsoft/callback`,
    tenantId: process.env.MS_TENANT || '',
    prompt: 'select_account',
  },
  async ({ extraParams, profile }) => {
    const { id_token } = extraParams
    const idToken = decodeToken(id_token)
    const roles = idToken?.roles || []
    const user = await getUser(profile._json.email)

    return {
      ...profile,
      employeeId: user.EmployeeId,
      isActive: user.IsActive,
      roles,
    }
  },
)

authenticator.use(microsoftStrategy)